1. Introduction

Apache Shiro is a powerful and easy-to-use Java security framework that performs authentication, authorization, cryptography, and session management.

This plugin enables access control on controller actions via a set of annotations. Security checks will be performed before an action is invoked. Annotated actions will be executed if the user meets the security criteria, otherwise execution is aborted. The plugin assumes sensible defaults where needed but also lets you customize behavior.

1.1. Usage

Controller actions must be annotated with any of the following annotations

  • @org.apache.shiro.authz.annotation.RequiresAuthentication - Requires the current Subject to have been authenticated during their current session for the annotated class/instance/method to be accessed or invoked.

  • @org.apache.shiro.authz.annotation.RequiresUser - Requires the current Subject to be an authenticated user during their current session for the annotated class/instance/method to be accessed or invoked.

  • @org.apache.shiro.authz.annotation.RequiresGuest - Requires the current Subject to be a "guest", that is, they are not authenticated or remembered from a previous session for the annotated class/instance/method to be accessed or invoked.

  • @org.apache.shiro.authz.annotation.RequiresPermissions - Requires the current executor’s Subject to imply a particular permission in order to execute the annotated method. If the executor’s associated Subject determines that the executor does not imply the specified permission, the method will not be executed.

  • @org.apache.shiro.authz.annotation.RequiresRoles - Requires the currently executing Subject to have all of the specified roles. If they do not have the role(s), the method will not be executed.

The annotations may be applied at the class level, in which case all actions will inherit those constraints. Annotations applied to methods/closures override those applied at the class level, for example

1
2
3
4
5
6
7
8
9
10
11
12
13
import griffon.core.artifact.GriffonController
import griffon.metadata.ArtifactProviderFor
import org.apache.shiro.authz.annotation.*

@RequiresAuthentication
@ArtifactProviderFor(GriffonController)
class PrinterController {
    @RequiresPermission('printer:print')
    void print() { ... }

    @RequiresRoles('administrator')
    void configure() { ... }
}

Anyone making use of PrinterController must be aun authenticated user. Everyone with the permissions printer:print may call the print action. Only those users that have been authenticated and posses the administrator role are able to call the configure action.

Apache Shiro’s Java Authentication Guide presents the basic vocabulary and behavior required to authenticate a user into the system. In particular, the SecurityUtils class is used to store the current Subject. You may also inject an Subject instance, like so

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import griffon.core.artifact.GriffonController
import griffon.metadata.ArtifactProviderFor
import org.apache.shiro.authz.annotation.*
import org.apache.shiro.authc.UsernamePasswordToken
import org.apache.shiro.subject.Subject

@RequiresAuthentication
@ArtifactProviderFor(GriffonController)
class LoginController {
    LoginModel model

    @Inject
    private Subject subject

    @RequiresGuest
    void login() {
        UsernamePasswordToken token = new UsernamePasswordToken(
            model.username, model.password)
        subject.login(token)
    }

    @RequiresAuthentication
    void logout() {
        subject.logout()
    }
}

The login action will be executed when there’s no authenticated user while the logout action will be executed only if the user is currently authenticated.

Griffon version: 2.9.1

2. Configuration

The plugin requires an instance of org.apache.shiro.mgt.SecurityManager to work. The ShiroModule registers org.apache.shiro.mgt.DefaultSecurityManager as the default implementation that relies on a properties based org.apache.shiro.realm.Realm, whose default settings point to a file named shiro-users.properties that must be available in the classpath. The location of this resource may be changed too, defining a different value for shiro.realm.resource.path in the applications' configuration.

Security failures are handled by default by simply logging the failed attempt. This behavior can be changed too, for example displaying a dialog with a meaningful message. To change the default behavior configure a module binding for griffon.plugins.shiro.SecurityFailureHandler.

Its value should be a className implementing griffon.plugins.shiro.SecurityFailureHandler.

The following section was adapted from Grails Shiro plugin original by Peter Ledbrook (@pledbrook).

2.1. Fine-tuning the Access Control

The default Shiro setup provided this plugin is very flexible and powerful. It’s based on permission strings known as "wildcard permissions" that are simple to use, but in some ways difficult to understand because they are also very flexible.

2.1.1. About Wildcard Permissions

Let’s start with an example. Say you want to protect access to your company’s printers such that some people can print to particular printers, while others can find out what jobs are currently in the queue. The basic type of permission is therefore "printer", while we have two sub-types: "query" and "print". We also want to restrict access on a per-printer basis, so we then have a second sub-type that is the printer name. In wildcard permission format, the permission requirements are

printer:query:lp720 0
printer:print:epsoncolor

Notice how each part is separated by a colon? That’s how the wildcard permission format separates what it calls "parts". It’s also worth pointing out at this stage that Apache Shiro has no understanding of printer permissions - they are used and interpreted by the application.

So those are permission requirements. They state what permission is required to do something. In the above example, the first permission says that a user must have the right to query the "lp7200" printer. That’s just the application’s interpretation of the string, though. You still need to code the permission requirement into your application. A simple way to do this is in a condition:

if (subject.isPermitted('printer:query:lp7200')) {
    // Return the current jobs on printer lp7200
}

On the other side of the coin, you have permission assignments where you say what rights particular users have. In the quick start example, you saw a permission assignment in the BootStrap class.

Assignments look a lot like permission requirements, but they also support syntax for wildcards and specifying multiple types or sub-types. What do I mean by that? Well, imagine you want a user to have print access to all the printers in a company. You could assign all the permissions manually:

printer:print:lp7200
printer:print:epsoncolor
...

but this doesn’t scale well, particularly when new printers are added. You can instead use a wildcard:

printer:print:*

This does scale, because it covers any new printers as well. You could even allow access to all actions on all printers:

printer:*:*

or all actions on a single printer:

printer:*:lp7200

or even specific actions:

printer:query,print:lp7200

The '*' wildcard and ',' sub-type separator can be used in any part of the permission, even the first part as you saw in the BootStrap example.

One final thing to note about permission assignments: missing parts imply that the user has access to all values corresponding to that part. In other words,

printer:print

is equivalent to

printer:print:*

and

printer

is equivalent to

printer:*:*

However, you can only leave off parts from the end of the string, so this:

printer:lp7200

is not equivalent to

printer:*:lp7200

Permission assignments like these are typically done at the database level, although it depends on your realm implementation.

2.2. Build Configuration

2.2.1. Gradle

You have two options for configuring this plugin: automatic and manual.

Automatic

As long as the project has the org.codehaus.griffon.griffon plugin applied to it you may include the following snippet in build.gradle

dependencies {
    griffon 'org.codehaus.griffon.plugins:griffon-shiro-plugin:1.4.0'
}

The griffon plugin will take care of the rest given its configuration.

Manual

You will need to configure any of the following blocks depending on your setup

dependencies {
    compile 'org.codehaus.griffon.plugins:griffon-shiro-core:1.4.0'
}

2.2.2. Maven

First configure the griffon-shiro-plugin BOM in your POM file, by placing the following snippet before the <build> element

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.codehaus.griffon.plugins</groupId>
            <artifactId>griffon-shiro-plugin</artifactId>
            <version>1.4.0</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

Next configure dependencies as required by your particular setup

<dependency>
    <groupId>org.codehaus.griffon.plugins</groupId>
    <artifactId>griffon-shiro-core</artifactId>
</dependency>

3. Modules

The following sections display all bindings per module. Use this information to successfully override a binding on your own modules or to troubleshoot a module binding if the wrong type has been applied by the Griffon runtime.

3.1. Shiro

Module name: shiro

bind(SecurityManager.class)
    .toProvider(SecurityManagerProvider.class)
    .asSingleton();

bind(Subject.class)
    .toProvider(SubjectProvider.class)
    .asSingleton();

bind(SecurityFailureHandler.class)
    .to(DefaultSecurityFailureHandler.class)
    .asSingleton();

bind(ActionHandler.class)
    .to(ShiroActionHandler.class)
    .asSingleton();