SeamFramework.orgCommunity Documentation
The Seam Security API provides a multitude of security-related features for your Seam-based application, covering such areas as:
Authentication - an extensible, JAAS-based authentication layer that allows users to authenticate against any security provider.
Identity Management - an API for managing a Seam application's users and roles at runtime.
Authorization - an extremely comprehensive authorization framework, supporting user roles, persistent and rule-based permissions, and a pluggable permission resolver for easily implementing customised security logic.
Permission Management - a set of built-in Seam components to allow easy management of an application's security policy.
CAPTCHA support - to assist in the prevention of automated software/scripts abusing your Seam-based site.
And much more
This chapter will cover each of these features in detail.
In some situations it may be necessary to disable Seam Security, for example during unit tests. This can be done by
calling the static method Identity.setSecurityEnabled(false) to disable security checks. Doing this
prevents any security checks being performed for the following:
Entity Security
Hibernate Security Interceptor
Seam Security Interceptor
Page restrictions
The authentication features provided by Seam Security are built upon JAAS (Java Authentication and Authorization Service), and as such provide a robust and highly configurable API for handling user authentication. However, for less complex authentication requirements Seam offers a much more simplified method of authentication that hides the complexity of JAAS.
The simplified authentication method provided by Seam uses a built-in JAAS login module, SeamLoginModule, which
delegates authentication to one of your own Seam components. This login module is already configured inside Seam as
part of a default application policy and as such does not require any additional configuration files. It allows you to
write an authentication method using the entity classes that are provided by your own application, or alternatively to
authenticate with some other third party provider. Configuring this simplified form of authentication requires the
identity component to be configured in components.xml:
<components xmlns="http://jboss.com/products/seam/components"
xmlns:core="http://jboss.com/products/seam/core"
xmlns:security="http://jboss.com/products/seam/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation=
"http://jboss.com/products/seam/components http://jboss.com/products/seam/components-2.1.xsd
http://jboss.com/products/seam/security http://jboss.com/products/seam/security-2.1.xsd">
<security:identity authenticate-method="#{authenticator.authenticate}"/>
</components>
The EL expression #{authenticator.authenticate} is a method binding that indicates
the authenticate method of the authenticator component will be used
to authenticate the user.
The authenticate-method property specified for identity in
components.xml specifies which method will be used by SeamLoginModule
to authenticate users. This method takes no parameters, and is expected to return a boolean, which indicates
whether authentication is successful or not. The user's username and password can be obtained from
Credentials.getUsername() and Credentials.getPassword(),
respectively. Any roles that the user is a member of should be assigned using
Identity.addRole(). Here's a complete example of an authentication method
inside a POJO component:
@Name("authenticator")
public class Authenticator {
@In EntityManager entityManager;
@In Credentials credentials;
@In Identity identity;
public boolean authenticate() {
try {
User user = (User) entityManager.createQuery(
"from User where username = :username and password = :password")
.setParameter("username", credentials.getUsername())
.setParameter("password", credentials.getPassword())
.getSingleResult();
if (user.getRoles() != null) {
for (UserRole mr : user.getRoles())
identity.addRole(mr.getName());
}
return true;
}
catch (NoResultException ex) {
return false;
}
}
}
In the above example, both User and UserRole are application-specific
entity beans. The roles parameter is populated with the roles that the user is a member
of, which should be added to the Set as literal string values, e.g. "admin", "user".
In this case, if the user record is not found and a NoResultException thrown, the
authentication method returns false to indicate the authentication failed.
When writing an authenticator method, it is important that it is kept minimal and free from any side-effects. This is because there is no guarantee as to how many times the authenticator method will be called by the security API, and as such it may be invoked multiple times during a single request. Because of this, any special code that should execute upon a successful or failed authentication should be written by implementing an event observer. See the section on Security Events further down in this chapter for more information about which events are raised by Seam Security.
The Identity.addRole() method behaves differently depending on whether the current
session is authenticated or not. If the session is not authenticated, then addRole()
should only be called during the authentication process. When called here, the
role name is placed into a temporary list of pre-authenticated roles. Once authentication is successful,
the pre-authenticated roles then become "real" roles, and calling Identity.hasRole()
for those roles will then return true. The following sequence diagram represents the list of pre-authenticated
roles as a first class object to show more clearly how it fits in to the authentication process.

If the current session is already authenticated, then calling Identity.addRole() will
have the expected effect of immediately granting the specified role to the current user.
Say for example, that upon a successful login that some user statistics must be
updated. This would be done by writing an event observer for the
org.jboss.seam.security.loginSuccessful event, like this:
@In UserStats userStats;
@Observer("org.jboss.seam.security.loginSuccessful")
public void updateUserStats()
{
userStats.setLastLoginDate(new Date());
userStats.incrementLoginCount();
}
This observer method can be placed anywhere, even in the Authenticator component itself. You can find more information about security-related events later in this chapter.
The credentials component provides both username and password
properties, catering for the most common authentication scenario. These properties can be bound directly to the
username and password fields on a login form. Once these properties are set, calling
identity.login() will authenticate the user using the provided credentials.
Here's an example of a simple login form:
<div>
<h:outputLabel for="name" value="Username"/>
<h:inputText id="name" value="#{credentials.username}"/>
</div>
<div>
<h:outputLabel for="password" value="Password"/>
<h:inputSecret id="password" value="#{credentials.password}"/>
</div>
<div>
<h:commandButton value="Login" action="#{identity.login}"/>
</div>
Similarly, logging out the user is done by calling #{identity.logout}. Calling this
action will clear the security state of the currently authenticated user, and invalidate the user's session.
So to sum up, there are the three easy steps to configure authentication:
Configure an authentication method in components.xml.
Write an authentication method.
Write a login form so that the user can authenticate.
Seam Security supports the same kind of "Remember Me" functionality that is commonly encountered in many online web-based applications. It is actually supported in two different "flavours", or modes - the first mode allows the username to be stored in the user's browser as a cookie, and leaves the entering of the password up to the browser (many modern browsers are capable of remembering passwords).
The second mode supports the storing of a unique token in a cookie, and allows a user to authenticate automatically upon returning to the site, without having to provide a password.
Automatic client authentication with a persistent cookie stored on the client machine is dangerous. While convenient for users, any cross-site scripting security hole in your website would have dramatically more serious effects than usual. Without the authentication cookie, the only cookie to steal for an attacker with XSS is the cookie of the current session of a user. This means the attack only works when the user has an open session - which should be a short timespan. However, it is much more attractive and dangerous if an attacker has the possibility to steal a persistent Remember Me cookie that allows him to login without authentication, at any time. Note that this all depends on how well you protect your website against XSS attacks - it's up to you to make sure that your website is 100% XSS safe - a non-trival achievement for any website that allows user input to be rendered on a page.
Browser vendors recognized this issue and introduced a "Remember Passwords" feature - today almost all browsers support this. Here, the browser remembers the login username and password for a particular website and domain, and fills out the login form automatically when you don't have an active session with the website. If you as a website designer then offer a convenient login keyboard shortcut, this approach is almost as convenient as a "Remember Me" cookie and much safer. Some browsers (e.g. Safari on OS X) even store the login form data in the encrypted global operation system keychain. Or, in a networked environment, the keychain can be transported with the user (between laptop and desktop for example), while browser cookies are usually not synchronized.
To summarize: While everyone is doing it, persistent "Remember Me" cookies with automatic authentication are a bad practice and should not be used. Cookies that "remember" only the users login name, and fill out the login form with that username as a convenience, are not an issue.
To enable the remember me feature for the default (safe, username only) mode, no special configuration is required.
In your login form, simply bind the remember me checkbox to rememberMe.enabled, like in the following
example:
<div>
<h:outputLabel for="name" value="User name"/>
<h:inputText id="name" value="#{credentials.username}"/>
</div>
<div>
<h:outputLabel for="password" value="Password"/>
<h:inputSecret id="password" value="#{credentials.password}" redisplay="true"/>
</div>
<div class="loginRow">
<h:outputLabel for="rememberMe" value="Remember me"/>
<h:selectBooleanCheckbox id="rememberMe" value="#{rememberMe.enabled}"/>
</div>
To use the automatic, token-based mode of the remember me feature, you must first configure a token store. The
most common scenario is to store these authentication tokens within a database (which Seam supports), however it
is possible to implement your own token store by implementing the org.jboss.seam.security.TokenStore
interface. This section will assume you will be using the provided JpaTokenStore implementation
to store authentication tokens inside a database table.
The first step is to create a new Entity which will contain the tokens. The following example shows a possible structure that you may use:
@Entity
public class AuthenticationToken implements Serializable {
private Integer tokenId;
private String username;
private String value;
@Id @GeneratedValue
public Integer getTokenId() {
return tokenId;
}
public void setTokenId(Integer tokenId) {
this.tokenId = tokenId;
}
@TokenUsername
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
@TokenValue
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
As you can see from this listing, a couple of special annotations, @TokenUsername and
@TokenValue are used to configure the username and token properties of the entity. These
annotations are required for the entity that will contain the authentication tokens.
The next step is to configure JpaTokenStore to use this entity bean to store and retrieve
authentication tokens. This is done in components.xml by specifying the token-class
attribute:
<security:jpa-token-store token-class="org.jboss.seam.example.seamspace.AuthenticationToken"/>
Once this is done, the last thing to do is to configure the RememberMe component in
components.xml also. Its mode should be set to autoLogin:
<security:remember-me mode="autoLogin"/>
That is all that is required - automatic authentication will now occur for users revisiting your site (as long as they check the "remember me" checkbox).
To prevent users from receiving the default error page in response to a security error, it's recommended that
pages.xml is configured to redirect security errors to a more "pretty" page. The two
main types of exceptions thrown by the security API are:
NotLoggedInException - This exception is thrown if the user attempts to access a
restricted action or page when they are not logged in.
AuthorizationException - This exception is only thrown if the user is already logged in,
and they have attempted to access a restricted action or page for which they do not have the necessary
privileges.
In the case of a NotLoggedInException, it is recommended that the user is redirected to
either a login or registration page so that they can log in. For an AuthorizationException,
it may be useful to redirect the user to an error page. Here's an example of a pages.xml
file that redirects both of these security exceptions:
<pages>
...
<exception class="org.jboss.seam.security.NotLoggedInException">
<redirect view-id="/login.xhtml">
<message>You must be logged in to perform this action</message>
</redirect>
</exception>
<exception class="org.jboss.seam.security.AuthorizationException">
<end-conversation/>
<redirect view-id="/security_error.xhtml">
<message>You do not have the necessary security privileges to perform this action.</message>
</redirect>
</exception>
</pages>
Most web applications require even more sophisticated handling of login redirection, so Seam includes some special functionality for handling this problem.
You can ask Seam to redirect the user to a login screen when an unauthenticated user tries to access a particular view (or wildcarded view id) as follows:
<pages login-view-id="/login.xhtml">
<page view-id="/members/*" login-required="true"/>
...
</pages>
This is less of a blunt instrument than the exception handler shown above, but should probably be used in conjunction with it.
After the user logs in, we want to automatically send them back where they came from, so
they can retry the action that required logging in. If you add the following event listeners
to components.xml, attempts to access a restricted view while not logged
in will be remembered, so that upon the user successfully logging in they will be redirected
to the originally requested view, with any page parameters that existed in the original
request.
<event type="org.jboss.seam.security.notLoggedIn">
<action execute="#{redirect.captureCurrentView}"/>
</event>
<event type="org.jboss.seam.security.postAuthenticate">
<action execute="#{redirect.returnToCapturedView}"/>
</event>
Note that login redirection is implemented as a conversation-scoped mechanism, so don't end
the conversation in your authenticate() method.
Although not recommended for use unless absolutely necessary, Seam provides means for authenticating
using either HTTP Basic or HTTP Digest (RFC 2617) methods. To use either form of authentication,
the authentication-filter component must be enabled in components.xml:
<web:authentication-filter url-pattern="*.seam" auth-type="basic"/>
To enable the filter for basic authentication, set auth-type to basic,
or for digest authentication, set it to digest. If using digest authentication, the
key and realm must also be set:
<web:authentication-filter url-pattern="*.seam" auth-type="digest" key="AA3JK34aSDlkj" realm="My App"/>
The key can be any String value. The realm is the name of the
authentication realm that is presented to the user when they authenticate.
If using digest authentication, your authenticator class should extend the abstract class
org.jboss.seam.security.digest.DigestAuthenticator, and use the
validatePassword() method to validate the user's plain text password
against the digest request. Here is an example:
public boolean authenticate()
{
try
{
User user = (User) entityManager.createQuery(
"from User where username = :username")
.setParameter("username", identity.getUsername())
.getSingleResult();
return validatePassword(user.getPassword());
}
catch (NoResultException ex)
{
return false;
}
}
This section explores some of the advanced features provided by the security API for addressing more complex security requirements.
If you would rather not use the simplified JAAS configuration provided by the Seam Security API, you may
instead delegate to the default system JAAS configuration by providing a jaas-config-name
property in components.xml. For example, if you are using JBoss AS and wish to use
the other policy (which uses the UsersRolesLoginModule login module
provided by JBoss AS), then the entry in components.xml would look like this:
<security:identity jaas-config-name="other"/>
Please keep in mind that doing this does not mean that your user will be authenticated in whichever container your Seam application is deployed in. It merely instructs Seam Security to authenticate itself using the configured JAAS security policy.
Identity Management provides a standard API for the management of a Seam application's users and roles,
regardless of which identity store (database, LDAP, etc) is used on the backend. At the center
of the Identity Management API is the identityManager component, which provides
all the methods for creating, modifying and deleting users, granting and revoking roles, changing passwords,
enabling and disabling user accounts, authenticating users and listing users and roles.
Before it may be used, the identityManager must first be configured with one or more
IdentityStores. These components do the actual work of interacting with the backend
security provider, whether it be a database, LDAP server, or something else.

The identityManager component allows for separate identity stores to be configured
for authentication and authorization operations. This means that it is possible for users to
be authenticated against one identity store, for example an LDAP directory, yet have their roles
loaded from another identity store, such as a relational database.
Seam provides two IdentityStore implementations out of the box;
JpaIdentityStore uses a relational database to store user and role information,
and is the default identity store that is used if nothing is explicitly configured in the
identityManager component. The other implementation that is provided is
LdapIdentityStore, which uses an LDAP directory to store users and roles.
There are two configurable properties for the identityManager component -
identityStore and roleIdentityStore. The value for these
properties must be an EL expression referring to a Seam component implementing the
IdentityStore interface. As already mentioned,
if left unconfigured then JpaIdentityStore will be assumed by default. If
only the identityStore property is configured, then the same value will be used for
roleIdentityStore also. For example, the following entry in
components.xml will configure identityManager to use
an LdapIdentityStore for both user-related and role-related operations:
<security:identity-manager identity-store="#{ldapIdentityStore}"/>
The following example configures identityManager to use an LdapIdentityStore
for user-related operations, and JpaIdentityStore for role-related operations:
<security:identity-manager
identity-store="#{ldapIdentityStore}"
role-identity-store="#{jpaIdentityStore}"/>
The following sections explain both of these identity store implementations in greater detail.
This identity store allows for users and roles to be stored inside a relational database. It is designed to be as unrestrictive as possible in regards to database schema design, allowing a great deal of flexibility in the underlying table structure. This is achieved through the use of a set of special annotations, allowing entity beans to be configured to store user and role records.
JpaIdentityStore requires that both the user-class and
role-class properties are configured. These properties should refer to the
entity classes that are to be used to store both user and role records, respectively. The following
example shows the configuration from components.xml in the SeamSpace example:
<security:jpa-identity-store
user-class="org.jboss.seam.example.seamspace.MemberAccount"
role-class="org.jboss.seam.example.seamspace.MemberRole"/>
As already mentioned, a set of special annotations are used to configure entity beans for storing users and roles. The following table lists each of the annotations, and their descriptions.
Table 15.1. User Entity Annotations
|
Annotation |
Status |
Description |
|---|---|---|
|
|
Required |
This annotation marks the field or method containing the user's username. |
|
|
Required |
This annotation marks the field or method containing the user's password. It allows a <!-- <br/> --><span class="java_plain">@</span><!-- <br/> --><span class="java_type">UserPassword</span><!-- <br/> --><span class="java_separator">(</span><!-- <br/> --><span class="java_plain">hash </span><!-- <br/> --><span class="java_operator">=</span><!-- <br/> --><span class="java_plain"> </span><!-- <br/> --><span class="java_literal">"md5"</span><!-- <br/> --><span class="java_separator">)</span>
<!-- --><br/><span class="java_keyword">public</span><span class="java_plain"> </span><span class="java_type">String</span><span class="java_plain"> getPasswordHash</span><span class="java_separator">()</span><span class="java_plain"> </span><span class="java_separator">{</span><span class="java_plain"> </span>
<!-- --><br/><span class="java_plain"> </span><span class="java_keyword">return</span><span class="java_plain"> passwordHash</span><span class="java_separator">;</span><span class="java_plain"> </span>
<!-- --><br/><span class="java_separator">}</span>
|
|
|
Optional |
This annotation marks the field or method containing the user's first name. |
|
|
Optional |
This annotation marks the field or method containing the user's last name. |
|
|
Optional |
This annotation marks the field or method containing the enabled status of the user. This should be a boolean property, and if not present then all user accounts are assumed to be enabled. |
|
|
Required |
This annotation marks the field or method containing the roles of the user. This property will be described in more detail further down. |
Table 15.2. Role Entity Annotations
|
Annotation |
Status |
Description |
|---|---|---|
|
|
Required |
This annotation marks the field or method containing the name of the role. |
|
|
Optional |
This annotation marks the field or method containing the group memberships of the role. |
|
|
Optional |
This annotation marks the field or method indicating whether the role is conditional or not. Conditional roles are explained later in this chapter. |
As mentioned previously, JpaIdentityStore is designed to be as flexible as possible when
it comes to the database schema design of your user and role tables. This section looks at a number of
possible database schemas that can be used to store user and role records.
In this bare minimal example, a simple user and role table are linked via a
many-to-many relationship using a cross-reference table named UserRoles.

@Entity
public class User {
private Integer userId;
private String username;
private String passwordHash;
private Set<Role> roles;
@Id @GeneratedValue
public Integer getUserId() { return userId; }
public void setUserId(Integer userId) { this.userId = userId; }
@UserPrincipal
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
@UserPassword(hash = "md5")
public String getPasswordHash() { return passwordHash; }
public void setPasswordHash(String passwordHash) { this.passwordHash = passwordHash; }
@UserRoles
@ManyToMany(targetEntity = Role.class)
@JoinTable(name = "UserRoles",
joinColumns = @JoinColumn(name = "UserId"),
inverseJoinColumns = @JoinColumn(name = "RoleId"))
public Set<Role> getRoles() { return roles; }
public void setRoles(Set<Role> roles) { this.roles = roles; }
}
@Entity
public class Role {
private Integer roleId;
private String rolename;
@Id @Generated
public Integer getRoleId() { return roleId; }
public void setRoleId(Integer roleId) { this.roleId = roleId; }
@RoleName
public String getRolename() { return rolename; }
public void setRolename(String rolename) { this.rolename = rolename; }
}This example builds on the above minimal example by including all of the optional fields, and allowing group memberships for roles.

@Entity
public class User {
private Integer userId;
private String username;
private String passwordHash;
private Set<Role> roles;
private String firstname;
private String lastname;
private boolean enabled;
@Id @GeneratedValue
public Integer getUserId() { return userId; }
public void setUserId(Integer userId) { this.userId = userId; }
@UserPrincipal
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
@UserPassword(hash = "md5")
public String getPasswordHash() { return passwordHash; }
public void setPasswordHash(String passwordHash) { this.passwordHash = passwordHash; }
@UserFirstName
public String getFirstname() { return firstname; }
public void setFirstname(String firstname) { this.firstname = firstname; }
@UserLastName
public String getLastname() { return lastname; }
public void setLastname(String lastname) { this.lastname = lastname; }
@UserEnabled
public boolean isEnabled() { return enabled; }
public void setEnabled(boolean enabled) { this.enabled = enabled; }
@UserRoles
@ManyToMany(targetEntity = Role.class)
@JoinTable(name = "UserRoles",
joinColumns = @JoinColumn(name = "UserId"),
inverseJoinColumns = @JoinColumn(name = "RoleId"))
public Set<Role> getRoles() { return roles; }
public void setRoles(Set<Role> roles) { this.roles = roles; }
}
@Entity
public class Role {
private Integer roleId;
private String rolename;
private boolean conditional;
@Id @Generated
public Integer getRoleId() { return roleId; }
public void setRoleId(Integer roleId) { this.roleId = roleId; }
@RoleName
public String getRolename() { return rolename; }
public void setRolename(String rolename) { this.rolename = rolename; }
@RoleConditional
public boolean isConditional() { return conditional; }
public void setConditional(boolean conditional) { this.conditional = conditional; }
@RoleGroups
@ManyToMany(targetEntity = Role.class)
@JoinTable(name = "RoleGroups",
joinColumns = @JoinColumn(name = "RoleId"),
inverseJoinColumns = @JoinColumn(name = "GroupId"))
public Set<Role> getGroups() { return groups; }
public void setGroups(Set<Role> groups) { this.groups = groups; }
}
When using JpaIdentityStore as the identity store implementation with IdentityManager,
a few events are raised as a result of invoking certain IdentityManager methods.
This event is raised in response to calling IdentityManager.createUser(). Just before the user
entity is persisted to the database, this event will be raised passing the entity instance as an event parameter.
The entity will be an instance of the user-class configured for JpaIdentityStore.
Writing an observer for this event may be useful for setting additional field values on the entity, which aren't set
as part of the standard createUser() functionality.
This event is also raised in response to calling IdentityManager.createUser(). However, it is
raised after the user entity has already been persisted to the database. Like the EVENT_PRE_PERSIST_USER
event, it also passes the entity instance as an event parameter. It may be useful to observe this event if you also
need to persist other entities that reference the user entity, for example contact detail records or other user-specific
data.
This identity store implementation is designed for working with user records stored in an LDAP directory. It is very highly configurable, allowing great flexibility in how both users and roles are stored in the directory. The following sections describe the configuration options for this identity store, and provide some configuration examples.
The following table describes the available properties that can be configured in components.xml for
LdapIdentityStore.
Table 15.3. LdapIdentityStore Configuration Properties
|
Property |
Default Value |
Description |
|---|---|---|
|
|
|
The address of the LDAP server. |
|
|
|
The port number that the LDAP server is listening on. |
|
|
|
The Distinguished Name (DN) of the context containing user records. |
|
|
|
This value is prefixed to the front of the username to locate the user's record. |
|
|
|
This value is appended to the end of the username to locate the user's record. |
|
|
|
The DN of the context containing role records. |
|
|
|
This value is prefixed to the front of the role name to form the DN for locating the role record. |
|
|
|
This value is appended to the role name to form the DN for locating the role record. |
|
|
|
This is the context used to bind to the LDAP server. |
|
|
|
These are the credentials (the password) used to bind to the LDAP server. |
|
|
|
This is the name of the attribute of the user record that contains the list of roles that the user is a member of. |
|
|
|
This boolean property indicates whether the role attribute of the user record is itself a distinguished name. |
|
|
|
Indicates which attribute of the user record contains the username. |
|
|
|
Indicates which attribute of the user record contains the user's password. |
|
|
|
Indicates which attribute of the user record contains the user's first name. |
|
|
|
Indicates which attribute of the user record contains the user's last name. |
|
|
|
Indicates which attribute of the user record contains the user's full (common) name. |
|
|
|
Indicates which attribute of the user record determines whether the user is enabled. |
|
|
|
Indicates which attribute of the role record contains the name of the role. |
|
|
|
Indicates which attribute determines the class of an object in the directory. |
|
|
|
An array of the object classes that new role records should be created as. |
|
|
|
An array of the object classes that new user records should be created as. |
The following configuration example shows how LdapIdentityStore may be configured for
an LDAP directory running on fictional host directory.mycompany.com. The users are stored
within this directory under the context ou=Person,dc=mycompany,dc=com, and are identified
using the uid attribute (which corresponds to their username). Roles are stored in their
own context, ou=Roles,dc=mycompany,dc=com and referenced from the user's entry via the
roles attribute. Role entries are identified by their common name (the cn attribute)
, which corresponds to the role name. In this example, users may be disabled by setting the value of their
enabled attribute to false.
<security:ldap-identity-store
server-address=