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.12.0
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.
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>2.1.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();