15.6. Authorization
This section describes the range of authorization mechanisms provided by the Seam Security API for securing access to components, component methods, and pages. If you wish to use any of the advanced features (for example, rule-based permissions), you may need to configure your
components.xml file — see the Configuration section previous.
15.6.1. Core concepts
Seam Security operates on the principle that users are granted roles or permissions, or both, which allow them to perform operations that are not permissible for users without the required security privileges. Each authorization mechanism provided by the Seam Security API is built upon this core concept of roles and permissions, with an extensible framework to provide multiple ways to secure application resources.
15.6.1.1. What is a role?
A role is a type of user that may have been granted certain privileges for performing one or more specific actions within an application. They are simple constructs, consisting of a name (such as "admin", "user", "customer", etc.) applied to users, or other roles. They are used to create logical user groups so that specific application privileges can be easily assigned.

15.6.1.2. What is a permission?
A permission is a privilege (sometimes once-off) for performing a single, specific action. You can build an application that operates solely on permissions, but roles are more convenient when granting privileges to groups. Permissions are slightly more complex in structure than roles, consisting of three "aspects"; a target, an action, and a recipient. The target of a permission is the object (or an arbitrary name or class) for which a particular action is allowed to be performed by a specific recipient (or user). For example, the user "Bob" may have permission to delete customer objects. In this case, the permission target may be "customer", the permission action would be "delete" and the recipient would be "Bob".

In this documentation, permissions are usually represented in the form
target:action, omitting the recipient. In reality, a recipient is always required.
15.6.2. Securing components
We will start with the simplest form of authorization: component security. First, we will look at the
@Restrict annotation.
Note
While the
@Restrict annotation is a powerful and flexible method for security components, it cannot support EL expressions. Therefore, we recommend using the typesafe equivalent (described later in this chapter) for its compile-time safety.
15.6.2.1. The @Restrict annotation
Seam components can be secured at either the method or the class level with the
@Restrict annotation. If both a method and its declaring class are annotated with @Restrict, the method restriction will take precedence and the class restriction will not apply. If a method invocation fails a security check, an exception will be thrown as per the contract for Identity.checkRestriction(). (See the section following for more information on Inline Restrictions.) Placing @Restrict on the component class itself is the equivalent of adding @Restrict to each of its methods.
An empty
@Restrict implies a permission check of componentName:methodName. Take for example the following component method:
@Name("account") public class AccountAction { @Restrict public void delete() { ... } }
In this example,
account:delete is the implied permission required to call the delete() method. This is equivalent to writing @Restrict("#{s:hasPermission('account','delete')}"). The following is another example:
@Restrict @Name("account") public class AccountAction { public void insert() { ... } @Restrict("#{s:hasRole('admin')}") public void delete() { ... } }
Here, the component class itself is annotated with
@Restrict. This means that any methods without an overriding @Restrict annotation require an implicit permission check. In this case, the insert() method requires a permission of account:insert, while the delete() method requires that the user is a member of the admin role.
Before we go further, we will address the
#{s:hasRole()} expression seen in the previous example. s:hasRole and s:hasPermission are EL functions that delegate to the correspondingly-named methods of the Identity class. These functions can be used within any EL expression throughout the entirety of the security API.
Being an EL expression, the value of the
@Restrict annotation may refer to any object within a Seam context. This is extremely useful when checking permissions for a specific object instance. Take the following example:
@Name("account") public class AccountAction { @In Account selectedAccount; @Restrict("#{s:hasPermission(selectedAccount,'modify')}") public void modify() { selectedAccount.modify(); } }
In this example, the
hasPermission() function call refers to selectedAccount. The value of this variable will be looked up from within the Seam context, and passed to the hasPermission() method in Identity. This will determine whether the user has the required permissions to modify the specified Account object.
15.6.2.2. Inline restrictions
It is sometimes necessary to perform a security check in code, without using the
@Restrict annotation. To do so, use Identity.checkRestriction() to evaluate a security expression, like this:
public void deleteCustomer() { Identity.instance().checkRestriction("#{s:hasPermission(selectedCustomer, 'delete')}"); }
If the specified expression does not evaluate to
true, one of two exceptions occurs. If the user is not logged in, a NotLoggedInException is thrown. If the user is logged in, an AuthorizationException is thrown.
You can also call the
hasRole() and hasPermission() methods directly from Java code:
if (!Identity.instance().hasRole("admin")) throw new AuthorizationException("Must be admin to perform this action"); if (!Identity.instance().hasPermission("customer", "create")) throw new AuthorizationException("You may not create new customers");
15.6.3. Security in the user interface
A well-designed interface does not present a user with options they are not permitted to use. Seam Security allows conditional rendering of page sections or individual controls based on user privileges, using the same EL expressions that are used for component security.
In this section, we will go through some examples of interface security. Say we have a login form that we want rendered only if the user is not already logged in. We can write the following with the
identity.isLoggedIn() property:
<h:form class="loginForm" rendered="#{not identity.loggedIn}">
If the user is not logged in, the login form will be rendered — very straightforward. Say we also have a menu on this page, and we want some actions to be accessed only by users in the
manager role. One way you could write this is the following:
<h:outputLink action="#{reports.listManagerReports}" rendered="#{s:hasRole('manager')}"> Manager Reports </h:outputLink>
This, too, is straightforward — if the user is not a member of the
manager role, the outputLink will not be rendered. The rendered attribute can generally be used on the control itself, or on a surrounding <s:div> or <s:span> control.
A more complex example of conditional rendering might be the following situation: say you have a
h:dataTable control on a page, and you want to render action links on its records only for users with certain privileges. The s:hasPermission EL function lets us use an object parameter to determine whether the user has the necessary permission for that object. A dataTable with secured links might look like this:
<h:dataTable value="#{clients}" var="cl"> <h:column> <f:facet name="header">Name</f:facet> #{cl.name} </h:column> <h:column> <f:facet name="header">City</f:facet> #{cl.city} </h:column> <h:column> <f:facet name="header">Action</f:facet> <s:link value="Modify Client" action="#{clientAction.modify}" rendered="#{s:hasPermission(cl,'modify')"/> <s:link value="Delete Client" action="#{clientAction.delete}" rendered="#{s:hasPermission(cl,'delete')"/> </h:column> </h:dataTable>
15.6.4. Securing pages
To use page security, you will need a
pages.xml file. Page security is easy to configure: simply include a <restrict/> element in the page elements that you want to secure. If no explicit restriction is specified in the restrict element, access via a non-Faces (GET) request requires an implied /viewId.xhtml:render permission, and /viewId.xhtml:restore permission is required when any JSF postback (form submission) originates from the page. Otherwise, the specified restriction will be evaluated as a standard security expression. Some examples are:
<page view-id="/settings.xhtml"> <restrict/> </page>
This page requires an implied permission of
/settings.xhtml:render for non-Faces requests, and an implied permission of /settings.xhtml:restore for Faces requests.
<page view-id="/reports.xhtml"> <restrict>#{s:hasRole('admin')}</restrict> </page>
Both Faces and non-Faces requests to this page require that the user is a member of the
admin role.
15.6.5. Securing Entities
Seam Security also lets you apply security restrictions to certain actions (read, insert, update, and delete) for entities.
To secure all actions for an entity class, add a
@Restrict annotation on the class itself:
@Entity @Name("customer") @Restrict public class Customer { ... }
If no expression is specified in the
@Restrict annotation, the default action is a permission check of entity:action, where the permission target is the entity instance, and the action is either read, insert, update or delete.
You can also restrict certain actions by placing a
@Restrict annotation on the relevant entity life cycle method (annotated as follows):
@PostLoad— Called after an entity instance is loaded from the database. Use this method to configure areadpermission.@PrePersist— Called before a new instance of the entity is inserted. Use this method to configure aninsertpermission.@PreUpdate— Called before an entity is updated. Use this method to configure anupdatepermission.@PreRemove— Called before an entity is deleted. Use this method to configure adeletepermission.
The following example shows how an entity can be configured to perform a security check for
insert operations. Note that the method need not perform any action, but it must be annotated correctly.
@PrePersist @Restrict public void prePersist() {}
Note
You can also specify the callback method in
/META-INF/orm.xml:
<?xml version="1.0" encoding="UTF-8"?> <entity-mappings xmlns="http://java.sun.com/xml/ns/persistence/orm" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://java.sun.com/xml/ns/persistence/orm http://java.sun.com/xml/ns/persistence/orm_1_0.xsd" version="1.0"> <entity class="Customer"> <pre-persist method-name="prePersist" /> </entity> </entity-mappings>
You will still need to annotate the
prePersist() method on Customer with @Restrict.
The following configuration is based on the Seamspace example, and checks if the authenticated user has permission to insert a new
MemberBlog record. The entity being checked is automatically inserted into the working memory (in this case, MemberBlog):
rule InsertMemberBlog no-loop activation-group "permissions" when principal: Principal() memberBlog: MemberBlog(member : member -> (member.getUsername().equals(principal.getName()))) check: PermissionCheck(target == memberBlog, action == "insert", granted == false) then check.grant(); end;
This rule grants the permission
memberBlog:insert if the name of the currently authenticated user (indicated by the Principal fact) matches that of the member for whom the blog entry is being created. The principal: Principal() structure is a variable binding. It binds the instance of the Principal object placed in the working memory during authentication, and assigns it to a variable called principal. Variable bindings let the variable be referenced in other places, such as the following line, which compares the member name to the Principal name. For further details, refer to the JBoss Rules documentation.
Finally, install a listener class to integrate Seam Security with your JPA provider.
15.6.5.1. Entity security with JPA
Security checks for EJB3 entity beans are performed with an
EntityListener. Install this listener with the following META-INF/orm.xml file:
<?xml version="1.0" encoding="UTF-8"?> <entity-mappings xmlns="http://java.sun.com/xml/ns/persistence/orm" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://java.sun.com/xml/ns/persistence/orm http://java.sun.com/xml/ns/persistence/orm_1_0.xsd" version="1.0"> <persistence-unit-metadata> <persistence-unit-defaults> <entity-listeners> <entity-listener class="org.jboss.seam.security.EntitySecurityListener"/> </entity-listeners> </persistence-unit-defaults> </persistence-unit-metadata> </entity-mappings>
15.6.5.2. Entity security with a Managed Hibernate Session
If you use a Hibernate
SessionFactory configured with Seam, and use annotations or orm.xml, you do not need to make any changes to use entity security.
15.6.6. Typesafe Permission Annotations
Seam provides a number of alternative annotations to
@Restrict. These support arbitrary EL expressions differently, which gives them additional compile-time safety.
Seam comes with a set of annotations for standard CRUD-based permissions. The following annotations are provided in the
org.jboss.seam.annotations.security package:
- @Insert
- @Read
- @Update
- @Delete
To use these annotations, place them on the method or parameter for which you wish to perform a security check. When placed on a method, they specify a target class for which the permission will be checked. Take the following example:
@Insert(Customer.class) public void createCustomer() { ... }
Here, a permission check will be performed for the user to ensure that they have permission to create new
Customer objects. The target of the permission check is Customer.class (the actual java.lang.Class instance itself), and the action is the lower case representation of the annotation name, which in this example is insert.
You can annotate a component method's parameters in the same way, as follows. If you do this, you need not specify a permission target, since the parameter value itself will be the target of the permission check.
public void updateCustomer(@Update Customer customer) { ... }
To create your own security annotation, just annotate it with
@PermissionCheck. For example:
@Target({METHOD, PARAMETER}) @Documented @Retention(RUNTIME) @Inherited @PermissionCheck public @interface Promote { Class value() default void.class; }
If you wish to override the default permission action name (the lower case version of the annotation name) with another value, you can specify this within the
@PermissionCheck annotation:
@PermissionCheck("upgrade")
15.6.7. Typesafe Role Annotations
In addition to typsesafe permission annotation support, Seam Security provides typesafe role annotations that let you restrict access to component methods based on the role memberships of the currently authenticated user. Seam provides one such annotation (
org.jboss.seam.annotations.security.Admin) out of the box. This restricts access of a particular method to users that belong to the admin role, as long as such a role is supported by your application. To create your own role annotations, meta-annotate them with org.jboss.seam.annotations.security.RoleCheck as in the following example:
@Target({METHOD}) @Documented @Retention(RUNTIME) @Inherited @RoleCheck public @interface User { }
Any methods subsequently annotated with the
@User annotation will be automatically intercepted. The user will be checked for membership of the corresponding role name (the lower case version of the annotation name, in this case user).
15.6.8. The Permission Authorization Model
Seam Security provides an extensible framework for resolving application permissions. The following class diagram shows an overview of the main components of the permission framework:

The relevant classes are explained in more detail in the following sections.
15.6.8.1. PermissionResolver
An interface that provides methods for resolving individual object permissions. Seam provides the following built-in
PermissionResolver implementations, which are described in greater detail later in the chapter:
RuleBasedPermissionResolver— Resolves rule-based permission checks with Drools.PersistentPermissionResolver— Stores object permissions in a permanent store, such as a relational database.
15.6.8.1.1. Writing your own PermissionResolver
Implementing your own permission resolver is simple. The
PermissionResolver interface defines two methods that must be implemented, as seen in the following table. If your PermissionResolver is deployed in your Seam project, it will be scanned automatically during deployment and registered with the default ResolverChain.
Table 15.7. PermissionResolver interface
|
Return type
|
Method
|
Description
|
|---|---|---|
boolean
| hasPermission(Object target, String action)
|
This method resolves whether the currently authenticated user (obtained via a call to
Identity.getPrincipal()) has the permission specified by the target and action parameters. It returns true if the user has the specified permission, or false if they do not.
|
void
| filterSetByAction(Set<Object> targets, String action)
|
This method removes any objects from the specified set that would return
true if passed to the hasPermission() method with the same action parameter value.
|
Note
Because they are cached in the user's session, any custom
PermissionResolver implementations must adhere to several restrictions. Firstly, they cannot contain any state that is more fine-grained than the session scope, and the component itself should be either application- or session-scoped. Secondly, they must not use dependency injection, as they may be accessed from multiple threads simultaneously. For optimal performance, we recommend annotating with @BypassInterceptors to bypass Seam's interceptor stack altogether.
15.6.8.2. ResolverChain
A
ResolverChain contains an ordered list of PermissionResolvers, to resolve object permissions for a particular object class or permission target.
The default
ResolverChain consists of all permission resolvers discovered during application deployment. The org.jboss.seam.security.defaultResolverChainCreated event is raised (and the ResolverChain instance passed as an event parameter) when the default ResolverChain is created. This allows additional resolvers that were not discovered during deployment to be added, or for resolvers that are in the chain to be re-ordered or removed.
The following sequence diagram shows the interaction between the components of the permission framework during a permission check. A permission check can originate from a number of possible sources: the security interceptor, the
s:hasPermission EL function, or via an API call to Identity.checkPermission:

- 1. A permission check is initiated (either in code or via an EL expression), resulting in a call to
Identity.hasPermission(). - 1.1.
IdentityinvokesPermissionMapper.resolvePermission(), passing in the permission to be resolved. - 1.1.1.
PermissionMappermaintains aMapofResolverChaininstances, keyed by class. It uses this map to locate the correctResolverChainfor the permission's target object. Once it has the correctResolverChain, it retrieves the list ofPermissionResolvers it contains by callingResolverChain.getResolvers(). - 1.1.2. For each
PermissionResolverin theResolverChain, thePermissionMapperinvokes itshasPermission()method, passing in the permission instance to be checked. If thePermissionResolvers returntrue, the permission check has succeeded and thePermissionMapperalso returnstruetoIdentity. If none of thePermissionResolvers returntrue, then the permission check has failed.
15.6.9. RuleBasedPermissionResolver
One of the built-in permission resolvers provided by Seam. This evaluates permissions based on a set of Drools (JBoss Rules) security rules. Some advantages to the rule engine are a centralized location for the business logic used to evaluate user permissions, and speed — Drools algorithms are very efficient for evaluating large numbers of complex rules involving multiple conditions.
15.6.9.1. Requirements
If you want to use the rule-based permission features provided by Seam Security, Drools requires the following
JAR files to be distributed with your project:
- drools-api.jar
- drools-compiler.jar
- drools-core.jar
- janino.jar
- antlr-runtime.jar
- mvel2.jar
15.6.9.2. Configuration
The configuration for
RuleBasedPermissionResolver requires that a Drools rule base is first configured in components.xml. By default, it expects the rule base to be named securityRules, as per the following example:
<components xmlns="http://jboss.org/schema/seam/components" xmlns:core="http://jboss.org/schema/seam/core" xmlns:security="http://jboss.org/schema/seam/security" xmlns:drools="http://jboss.org/schema/seam/drools" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://jboss.org/schema/seam/core http://jboss.org/schema/seam/core-2.3.xsd http://jboss.org/schema/seam/components http://jboss.org/schema/seam/components-2.3.xsd http://jboss.org/schema/seam/drools http://jboss.org/schema/seam/drools-2.3.xsd http://jboss.org/schema/seam/security http://jboss.org/schema/seam/security-2.3.xsd"> <drools:rule-base name="securityRules"> <drools:rule-files> <value>/META-INF/security.drl</value> </drools:rule-files> </drools:rule-base> </components>
The default rule base name can be overridden by specifying the
security-rules property for RuleBasedPermissionResolver:
<security:rule-based-permission-resolver security-rules="#{prodSecurityRules}"/>
Once the
RuleBase component is configured, you must write the security rules.
15.6.9.3. Writing Security Rules
The first step to writing security rules is to create a new rule file in the
/META-INF directory of your application's jar file. This file should be named security.drl or similar, but can be named anything as long as it is configured correspondingly in components.xml.
We recommend the Drools documentation when you write your rules file. A simple example of rules file contents is:
package MyApplicationPermissions; import org.jboss.seam.security.permission.PermissionCheck; import org.jboss.seam.security.Role; rule CanUserDeleteCustomers when c: PermissionCheck(target == "customer", action == "delete") Role(name == "admin") then c.grant(); end
Here, the first thing we see is the package declaration. A package in Drools is a collection of rules. The package name does not relate to anything outside the scope of the rule base, so it can be given any name.
Next, we have several import statements for the
PermissionCheck and Role classes. These imports inform the rules engine that our rules will refer to these classes.
Finally, we have the rule code. Each rule within a package should have a unique name, usually to describe the rule's purpose. In this case our rule is called
CanUserDeleteCustomers and will be used to check whether a user is allowed to delete a customer record.
There are two distinct sections in the body of the rule definition. Rules have a left hand side (LHS) and a right hand side (RHS). The LHS is the conditional portion of the rule, that is, a list of conditions that must be satisfied for the rule to fire. The LHS is represented by the
when section. The RHS is the consequence or action section of the rule, which will only be fired if all conditions in the LHS are met. The RHS is represented by the then section. The end of the rule is denoted by the end line.
There are two conditions listed in the example LHS. The first condition is:
c: PermissionCheck(target == "customer", action == "delete")
More plainly, this condition states that, to be fulfilled, there must be a
PermissionCheck object with a target property equal to customer, and an action property equal to delete within the working memory.
Working memory is also known as a stateful session in Drools terminology. It is a session-scoped object containing the contextual information that the rules engine requires to make a decision about a permission check. Each time the
hasPermission() method is called, a temporary PermissionCheck object, or Fact, is inserted into the working memory. This PermissionCheck corresponds exactly to the permission being checked, so if you call hasPermission("account", "create"), a PermissionCheck object with a target equal to "account" and action equal to "create" will be inserted into the working memory for the duration of the permission check.
Other than the
PermissionCheck facts, there is also an org.jboss.seam.security.Role fact for each role that the authenticated user is a member of. These Role facts are synchronized with the user's authenticated roles at the beginning of every permission check. As a consequence, any Role object inserted into the working memory during the course of a permission check will be removed before the next permission check occurs, unless the authenticated user is actually a member of that role. The working memory also contains the java.security.Principal object created as a result of the authentication process.
You can insert additional long-lived facts into the working memory by calling
RuleBasedPermissionResolver.instance().getSecurityContext().insert (), which passes the object as a parameter. Role objects are the exception, here, since they are synchronized at the start of each permission check.
To return to our simple example, the first line of our LHS is prefixed with
c:. This is a variable binding, and is used to refer back to the object matching the condition (in this case, the PermissionCheck). The second line of the LHS is:
Role(name == "admin")
This condition states that there must be a
Role object with a name of "admin" within the working memory. So, if you are checking for the customer:delete permission and the user is a member of the admin role, this rule will fire.
The RHS shows us the consequence of the rule firing:
c.grant()
The RHS consists of Java code. In this case it invokes the
grant() method of the c object, which is a variable binding for the PermissionCheck object. Other than the name and action properties of the PermissionCheck object, there is also a granted property. This is initially set to false. Calling grant() on a PermissionCheck sets the granted property to true. This means the permission check succeeded, and the user has permission to carry out the action that prompted the permission check.
15.6.9.4. Non-String permission targets
So far, we have only looked at permission checks for String-literal permission targets. However, you can also write security rules for more complex permission targets. For example, say you want to write a security rule to allow your users to create blog comments. The following rule shows one way this can be expressed, by requiring that the target of the permission check be an instance of
MemberBlog, and that the currently authenticated user be a member of the user role:
rule CanCreateBlogComment no-loop activation-group "permissions" when blog: MemberBlog() check: PermissionCheck(target == blog, action == "create", granted == false) Role(name == "user") then check.grant(); end
15.6.9.5. Wildcard permission checks
It is possible to implement a wildcard permission check (which allows all actions for a given permission target), by omitting the
action constraint for the PermissionCheck in your rule, like so:
rule CanDoAnythingToCustomersIfYouAreAnAdmin when c: PermissionCheck(target == "customer") Role(name == "admin") then c.grant(); end;
This rule allows users with the
admin role to perform any action for any customer permission check.
15.6.10. PersistentPermissionResolver
Another built-in permission resolver provided by Seam,
PersistentPermissionResolver, allows permissions to be loaded from persistent storage, such as a relational database. This permission resolver provides Access Control List-style instance-based security, allowing specific object permissions to be assigned to individual users and roles. It also allows persistent, arbitrarily-named permission targets (which are not necessarily object/class based) to be assigned in the same way.
15.6.10.1. Configuration
To use
PersistentPermissionResolver, you must configure a valid PermissionStore in components.xml. If this is not configured, the PersistentPermissionResolver will attempt to use the default permission store, Section 15.4.2.4, “JpaIdentityStore Events”. To use a permission store other than the default, configure the permission-store property as follows:
<security:persistent-permission-resolver permission-store="#{myCustomPermissionStore}"/>
15.6.10.2. Permission Stores
PersistentPermissionResolver requires a permission store to connect to the back-end storage where permissions are persisted. Seam provides one PermissionStore implementation out of the box, JpaPermissionStore, which stores permissions inside a relational database. You can write your own permission store by implementing the PermissionStore interface, which defines the following methods:
Table 15.8. PermissionStore interface
|
Return type
|
Method
|
Description
|
|---|---|---|
List<Permission>
| listPermissions(Object target)
|
This method should return a
List of Permission objects representing all the permissions granted for the specified target object.
|
List<Permission>
| listPermissions(Object target, String action)
|
This method should return a
List of Permission objects representing all the permissions with the specified action granted for the specified target object.
|
List<Permission>
| listPermissions(Set<Object> targets, String action)
|
This method should return a
List of Permission objects representing all the permissions with the specified action granted for the specified set of target objects.
|
boolean
| grantPermission(Permission)
|
This method should persist the specified
Permission object to the back-end storage, and return true if successful.
|
boolean
| grantPermissions(List<Permission> permissions)
|
This method should persist all of the
Permission objects contained in the specified List, and return true if successful.
|
boolean
| revokePermission(Permission permission)
|
This method should remove the specified
Permission object from persistent storage.
|
boolean
| revokePermissions(List<Permission> permissions)
|
This method should remove all of the
Permission objects in the specified list from persistent storage.
|
List<String>
| listAvailableActions(Object target)
|
This method should return a list of all available actions (as Strings) for the class of the specified target object. It is used in conjunction with permission management to build the user interface for granting specific class permissions.
|
15.6.10.3. JpaPermissionStore
The Seam-provided default
PermissionStore implementation, which stores permissions in a relational database. It must be configured with either one or two entity classes for storing user and role permissions before it can be used. These entity classes must be annotated with a special set of security annotations to configure the entity properties that correspond to various aspects of the stored permissions.
If you want to use the same entity (that is, a single database table) to store both user and role permissions, then you only need to configure the
user-permission-class property. To user separate tables for user and role permission storage, you must also configure the role-permission-class property.
For example, to configure a single entity class to store both user and role permissions:
<security:jpa-permission-store user-permission-class="com.acme.model.AccountPermission"/>
To configure separate entity classes for storing user and role permissions:
<security:jpa-permission-store user-permission-class="com.acme.model.UserPermission" role-permission-class="com.acme.model.RolePermission"/>
15.6.10.3.1. Permission annotations
The entity classes that contain the user and role permissions must be configured with a special set of annotations in the
org.jboss.seam.annotations.security.permission package. The following table describes these annotations:
Table 15.9. Entity Permission annotations
|
Annotation
|
Target
|
Description
|
|---|---|---|
@PermissionTarget
| FIELD,METHOD
|
This annotation identifies the entity property containing the permission target. The property should be of type
java.lang.String.
|
@PermissionAction
| FIELD,METHOD
|
This annotation identifies the entity property containing the permission action. The property should be of type
java.lang.String.
|
@PermissionUser
| FIELD,METHOD
|
This annotation identifies the entity property containing the recipient user for the permission. It should be of type
java.lang.String and contain the user's username.
|
@PermissionRole
| FIELD,METHOD
|
This annotation identifies the entity property containing the recipient role for the permission. It should be of type
java.lang.String and contain the role name.
|
@PermissionDiscriminator
| FIELD,METHOD
|
This annotation should be used when the same entity/table stores both user and role permissions. It identifies the property of the entity being used to discriminate between user and role permissions. By default, if the column value contains the string literal
user, then the record will be treated as a user permission. If it contains the string literal role, it will be treated as a role permission. You can also override these defaults by specifying the userValue and roleValue properties within the annotation. For example, to use u and r instead of user and role, write the annotation like so:
@PermissionDiscriminator( userValue = "u", roleValue = "r") |
15.6.10.3.2. Example Entity
The following is an example of an entity class that stores both user and role permissions, taken from the Seamspace example.
@Entity public class AccountPermission implements Serializable { private Integer permissionId; private String recipient; private String target; private String action; private String discriminator; @Id @GeneratedValue public Integer getPermissionId() { return permissionId; } public void setPermissionId(Integer permissionId) { this.permissionId = permissionId; } @PermissionUser @PermissionRole public String getRecipient() { return recipient; } public void setRecipient(String recipient) { this.recipient = recipient; } @PermissionTarget public String getTarget() { return target; } public void setTarget(String target) { this.target = target; } @PermissionAction public String getAction() { return action; } public void setAction(String action) { this.action = action; } @PermissionDiscriminator public String getDiscriminator() { return discriminator; } public void setDiscriminator(String discriminator) { this.discriminator = discriminator; } }
Here, the
getDiscriminator() method has been annotated with @PermissionDiscriminator, to allow JpaPermissionStore to determine which records represent user permissions and which represent role permissions. The getRecipient() method is annotated with both @PermissionUser and @PermissionRole. This means that the recipient property of the entity will either contain the name of the user or the name of the role, depending on the value of the discriminator property.
15.6.10.3.3. Class-specific Permission Configuration
The permissions included in the
org.jboss.seam.annotation.security.permission package can be used to configure a specific set of allowable permissions for a target class.
Table 15.10. Class Permission Annotations
|
Annotation
|
Target
|
Description
|
|---|---|---|
@Permissions
| TYPE
|
A container annotation, which can contain an array of
@Permission annotations.
|
@Permission
| TYPE
|
This annotation defines a single allowable permission action for the target class. Its
action property must be specified, and an optional mask property may also be specified if permission actions are to be persisted as bitmasked values (see section following).
|
The following shows the above annotations in use. They can also be seen in the SeamSpace example.
@Permissions({ @Permission(action = "view"), @Permission(action = "comment") }) @Entity public class MemberImage implements Serializable {...}
This example demonstrates how two allowable permission actions,
view and comment can be declared for the entity class MemberImage.
15.6.10.3.4. Permission masks
By default, multiple permissions for the same target object and recipient will be persisted as a single database record, with the
action property/column containing a list of granted actions, separated by commas. You can use a bitmasked integer value to store the list of permission actions — this reduces the amount of physical storage required to persist a large number of permissions.
For example, if recipient "Bob" is granted both the
view and comment permissions for a particular MemberImage (an entity bean) instance, then by default the action property of the permission entity will contain "view,comment", representing the two granted permission actions. Or, if you are using bitmasked values defined as follows:
@Permissions({ @Permission(action = "view", mask = 1), @Permission(action = "comment", mask = 2) }) @Entity public class MemberImage implements Serializable {...}
The
action property will contain "3" (with both the 1 bit and 2 bit switched on). For a large number of allowable actions for any particular target class, the storage required for the permission records is greatly reduced by using bitmasked actions.
Important
The
mask values specified must be powers of 2.
15.6.10.3.5. Identifier Policy
When storing or looking up permissions,
JpaPermissionStore must be able to uniquely identify specific object instances. To achieve this, an identifier strategy may be assigned to each target class so that unique identifier values can be generated. Each identifier strategy implementation knows how to generate unique identifiers for a particular type of class, and creating new identifier strategies is simple.
The
IdentifierStrategy interface is very simple, declaring only two methods:
public interface IdentifierStrategy { boolean canIdentify(Class targetClass); String getIdentifier(Object target); }
The first method,
canIdentify(), returns true if the identifier strategy is capable of generating a unique identifier for the specified target class. The second method, getIdentifier(), returns the unique identifier value for the specified target object.
Seam also provides two
IdentifierStrategy implementations, ClassIdentifierStrategy and EntityIdentifierStrategy, which are described in the sections following.
To explicitly configure a specific identifier strategy for a particular class, annotate the strategy with
org.jboss.seam.annotations.security.permission.Identifier and set the value to a concrete implementation of the IdentifierStrategy interface. You may also specify a name property. (The effect of this depends upon the IdentifierStrategy implementation used.)
15.6.10.3.6. ClassIdentifierStrategy
This identifier strategy generates unique identifiers for classes, and uses the value of the
name (if specified) in the @Identifier annotation. If no name property is provided, the identifier strategy attempts to use the component name of the class (if the class is a Seam component). It will create an identifier based on the class name (excluding the package name) as a last resort. For example, the identifier for the following class will be customer:
@Identifier(name = "customer") public class Customer {...}
The identifier for the following class will be
customerAction:
@Name("customerAction") public class CustomerAction {...}
Finally, the identifier for the following class will be
Customer:
public class Customer {...}
15.6.10.3.7. EntityIdentifierStrategy
This identifier strategy generates unique identifiers for entity beans. It concatenates the entity name (or otherwise configured name) with a string representation of the primary key value of the entity. The rules for generating the name section of the identifier are similar to those in
ClassIdentifierStrategy. The primary key value (that is, the entity ID) is obtained with the PersistenceProvider component, which can determine the value regardless of the persistence implementation being used in the Seam application. For entities not annotated with @Entity, you must explicitly configure the identifier strategy on the entity class itself, like this:
@Identifier(value = EntityIdentifierStrategy.class) public class Customer {...}
Assume we have the following entity class:
@Entity public class Customer { private Integer id; private String firstName; private String lastName; @Id public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } }
For a
Customer instance with an id value of 1, the value of the identifier would be Customer:1. If the entity class is annotated with an explicit identifier name, like so:
@Entity @Identifier(name = "cust") public class Customer {...}
Then a
Customer with an id value of 123 would have an identifier value of "cust:123".