15.4. Identity Management
Identity Management provides a standard API for managing a Seam application's users and roles, regardless of the identity store (database, LDAP, etc.) used in back-end operations. The
identityManager component is at the core of the Identity Management API, and provides all 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 use, the
identityManager must be configured with at least one IdentityStore. These components interact with the back-end security provider.

15.4.1. Configuring IdentityManager
The
identityManager component allows you to configure separate identity stores for authentication and authorization. This means that users can be authenticated against one identity store (for example, an LDAP directory), but have their roles loaded from another identity store (such as a relational database).
Seam provides two
IdentityStore implementations out of the box. The default, JpaIdentityStore, uses a relational database to store user and role information. The other implementation is LdapIdentityStore, which uses an LDAP directory to store users and roles.
The
identityManager component has two configurable properties: identityStore and roleIndentityStore. The value for these properties must be an EL expression that refers to a Seam component with the IdentityStore interface. If left unconfigured, the default (JpaIdentityStore) will be used. If only the identityStore property is configured, the same value will be used for roleIdentityStore. 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 each identity storage method in greater detail.
15.4.2. JpaIdentityStore
This method stores users and roles in a relational database. It is designed to allow flexible database design and table structure. A set of special annotations lets entity beans store user and role records.
15.4.2.1. Configuring JpaIdentityStore
Both
user-class and role-class must be configured before JpaIdentityStore can be used. These properties refer to the entity classes used to store user and role records, respectively. The following example shows the components.xml file from the SeamSpace example:
<security:jpa-identity-store user-class="org.jboss.seam.example.seamspace.MemberAccount" role-class="org.jboss.seam.example.seamspace.MemberRole"/>
15.4.2.2. Configuring the Entities
The following table describes the special annotations used to configure entity beans for user and role storage.
Table 15.1. User Entity Annotations
|
Annotation
|
Status
|
Description
|
|---|---|---|
@UserPrincipal
|
Required
|
This annotation marks the field or method containing the user's username.
|
@UserPassword
|
Required
|
This annotation marks the field or method containing the user's password. It allows a
hash algorithm to be specified for password hashing. Possible values for hash are md5, sha and none. For example:
@UserPassword(hash = "md5") public String getPasswordHash() { return passwordHash; }
It is possible to extend the
PasswordHash component to implement other hashing algorithms, if required.
|
@UserFirstName
|
Optional
|
This annotation marks the field or method containing the user's first name.
|
@UserLastName
|
Optional
|
This annotation marks the field or method containing the user's last name.
|
@UserEnabled
|
Optional
|
This annotation marks the field or method containing the enabled user status. This should be a Boolean property. If not present, all user accounts are assumed to be enabled.
|
@UserRoles
|
Required
|
This annotation marks the field or method containing the roles of the user. This property will be described in more detail in a later section.
|
Table 15.2. Role Entity Annotations
|
Annotation
|
Status
|
Description
|
|---|---|---|
@RoleName
|
Required
|
This annotation marks the field or method containing the name of the role.
|
@RoleGroups
|
Optional
|
This annotation marks the field or method containing the group memberships of the role.
|
@RoleConditional
|
Optional
|
This annotation marks the field or method that indicates whether a role is conditional. Conditional roles are explained later in this chapter.
|
15.4.2.3. Entity Bean Examples
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.
15.4.2.3.1. Minimal schema example
Here, 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; } }
15.4.2.3.2. Complex Schema Example
This example builds on the previous minimal example by including all 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; } }
15.4.2.4. JpaIdentityStore Events
When using
JpaIdentityStore with IdentityManager, several events are raised when certain IdentityManager methods are invoked.
15.4.2.4.1. JpaIdentityStore.EVENT_PRE_PERSIST_USER
This event is raised in response to calling
IdentityManager.createUser(). Just before the user entity is persisted to the database, this event is raised to pass the entity instance as an event parameter. The entity will be an instance of the user-class configured for JpaIdentityStore.
An observer can be useful, here, for setting entity field values that are not part of standard
createUser() functionality.
15.4.2.4.2. JpaIdentityStore.EVENT_USER_CREATED
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 need to persist other entities that reference the user entity, such as contact detail records or other user-specific data.
15.4.2.4.3. JpaIdentityStore.EVENT_USER_AUTHENTICATED
This event is raised when calling
IdentityManager.authenticate(). It passes the user entity instance as the event parameter, and is useful for reading additional properties from the user entity being authenticated.
15.4.3. LdapIdentityStore
This identity storage method is designed to work with user records stored in an LDAP directory. It is highly configurable, and allows very flexible directory storage of both users and roles. The following sections describe the configuration options for this identity store, and provide some configuration examples.
15.4.3.1. Configuring LdapIdentityStore
The following table describes the properties that can be configured in
components.xml for LdapIdentityStore.
Table 15.3. LdapIdentityStore Configuration Properties
|
Property
|
Default Value
|
Description
|
|---|---|---|
server-address
| localhost
|
The address of the LDAP server.
|
server-port
| 389
|
The port number that the LDAP server listens on.
|
user-context-DN
| ou=Person,dc=acme,dc=com
|
The Distinguished Name (DN) of the context containing user records.
|
user-DN-prefix
| uid=
|
This value is prefixed to the front of the username to locate the user's record.
|
user-DN-suffix
| ,ou=Person,dc=acme,dc=com
|
This value is appended to the end of the username to locate the user's record.
|
role-context-DN
| ou=Role,dc=acme,dc=com
|
The DN of the context containing role records.
|
role-DN-prefix
| cn=
|
This value is prefixed to the front of the role name to form the DN that locates the role record.
|
role-DN-suffix
| ,ou=Roles,dc=acme,dc=com
|
This value is appended to the role name to form the DN that locates the role record.
|
bind-DN
| cn=Manager,dc=acme,dc=com
|
This is the context used to bind to the LDAP server.
|
bind-credentials
| secret
|
These are the credentials (the password) used to bind to the LDAP server.
|
user-role-attribute
| roles
|
The attribute name of the user record containing the list of roles that the user is a member of.
|
role-attribute-is-DN
| true
|
This Boolean property indicates whether the role attribute of the user record is itself a distinguished name.
|
user-name-attribute
| uid
|
Indicates the user record attribute containing the username.
|
user-password-attribute
| userPassword
|
Indicates the user record attribute containing the user's password.
|
first-name-attribute
| null
|
Indicates the user record attribute containing the user's first name.
|
last-name-attribute
| sn
|
Indicates the user record attribute containing the user's last name.
|
full-name-attribute
| cn
|
Indicates the user record attribute containing the user's full (common) name.
|
enabled-attribute
| null
|
Indicates the user record attribute that determines whether the user is enabled.
|
role-name-attribute
| cn
|
Indicates the role record attribute containing the name of the role.
|
object-class-attribute
| objectClass
|
Indicates the attribute that determines the class of an object in the directory.
|
role-object-classes
| organizationalRole
|
An array of the object classes that new role records should be created as.
|
user-object-classes
| person,uidObject
|
An array of the object classes that new user records should be created as.
|
15.4.3.2. LdapIdentityStore Configuration Example
The following configuration example shows how
LdapIdentityStore can be configured for an LDAP directory running on fictional host directory.mycompany.com. The users are stored within this directory under the ou=Person,dc=mycompany,dc=com context, and are identified by the uid attribute (which corresponds to their username). Roles are stored in their own context, ou=Roles,dc=mycompany,dc=com, and are 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 can be disabled by setting the value of their enabled attribute to false.
<security:ldap-identity-store server-address="directory.mycompany.com" bind-DN="cn=Manager,dc=mycompany,dc=com" bind-credentials="secret" user-DN-prefix="uid=" user-DN-suffix=",ou=Person,dc=mycompany,dc=com" role-DN-prefix="cn=" role-DN-suffix=",ou=Roles,dc=mycompany,dc=com" user-context-DN="ou=Person,dc=mycompany,dc=com" role-context-DN="ou=Roles,dc=mycompany,dc=com" user-role-attribute="roles" role-name-attribute="cn" user-object-classes="person,uidObject" enabled-attribute="enabled" />
15.4.4. Writing your own IdentityStore
Writing your own identity store implementation allows you to authenticate and perform identity management operations against security providers that are not supported out of the box by Seam. You only need a single class that implements the
org.jboss.seam.security.management.IdentityStore interface to achieve this.
Refer to the JavaDoc about
IdentityStore for a description of the methods that must be implemented.
15.4.5. Authentication with Identity Management
If you use Identity Management features in your Seam application, then you do not need to provide an authenticator component (see previous Authentication section) to enable authentication. Simply omit the
authenticator-method from the identity configuration in components.xml, and the SeamLoginModule will use IdentityManager to authenticate your application's users without any special configuration.
15.4.6. Using IdentityManager
Access the
IdentityManager either by injecting it into your Seam component, like so:
@In IdentityManager identityManager;
or, through its static
instance() method:
IdentityManager identityManager = IdentityManager.instance();
The following table describes
IdentityManager's API methods:
Table 15.4. Identity Management API
|
Method
|
Returns
|
Description
|
|---|---|---|
createUser(String name, String password)
| boolean
|
Creates a new user account, with the specified name and password. Returns
true if successful; otherwise, returns false.
|
deleteUser(String name)
| boolean
|
Deletes the user account with the specified name. Returns
true if successful; otherwise, returns false.
|
createRole(String role)
| boolean
|
Creates a new role, with the specified name. Returns
true if successful; otherwise, returns false.
|
deleteRole(String name)
| boolean
|
Deletes the role with the specified name. Returns
true if successful; otherwise, returns false.
|
enableUser(String name)
| boolean
|
Enables the user account with the specified name. Accounts that are not enabled cannot authenticate. Returns
true if successful; otherwise, returns false.
|
disableUser(String name)
| boolean
|
Disables the user account with the specified name. Returns
true if successful; otherwise, returns false.
|
changePassword(String name, String password)
| boolean
|
Changes the password for the user account with the specified name. Returns
true if successful; otherwise, returns false.
|
isUserEnabled(String name)
| boolean
|
Returns
true if the specified user account is enabled; otherwise, returns false.
|
grantRole(String name, String role)
| boolean
|
Grants the specified role to the specified user or role. The role must already exist for it to be granted. Returns
true if the role is successfully granted, or false if the user has already been granted the role.
|
revokeRole(String name, String role)
| boolean
|
Revokes the specified role from the specified user or role. Returns
true if the specified user is a member of the role and it is successfully revoked, or false if the user is not a member of the role.
|
userExists(String name)
| boolean
|
Returns
true if the specified user exists, or false if it does not.
|
listUsers()
| List
|
Returns a list of all user names, sorted in alpha-numeric order.
|
listUsers(String filter)
| List
|
Returns a list of all user names filtered by the specified filter parameter, sorted in alpha-numeric order.
|
listRoles()
| List
|
Returns a list of all role names.
|
getGrantedRoles(String name)
| List
|
Returns a list of all roles explicitly granted to the specified user name.
|
getImpliedRoles(String name)
| List
|
Returns a list of all roles implicitly granted to the specified user name. Implicitly granted roles include those that are granted to the roles that the user is a member of, rather than granted directly to the user. For example, if the
admin role is a member of the user role, and a user is a member of the admin role, then the implied roles for the user are both the admin, and user roles.
|
authenticate(String name, String password)
| boolean
|
Authenticates the specified username and password using the configured Identity Store. Returns
true if successful or false if authentication failed. Successful authentication implies nothing beyond the return value of the method. It does not change the state of the Identity component - to perform a proper Seam log in the Identity.login() must be used instead.
|
addRoleToGroup(String role, String group)
| boolean
|
Adds the specified role as a member of the specified group. Returns true if the operation is successful.
|
removeRoleFromGroup(String role, String group)
| boolean
|
Removes the specified role from the specified group. Returns true if the operation is successful.
|
listRoles()
| List
|
Lists the names of all roles.
|
A calling user must have appropriate authorization to invoke methods on the Identity Management API. The following table describes the permission requirements for each of the methods in
IdentityManager. The permission targets listed below are literal String values.
Table 15.5. Identity Management Security Permissions
|
Method
|
Permission Target
|
Permission Action
|
|---|---|---|
createUser()
| seam.user
| create
|
deleteUser()
| seam.user
| delete
|
createRole()
| seam.role
| create
|
deleteRole()
| seam.role
| delete
|
enableUser()
| seam.user
| update
|
disableUser()
| seam.user
| update
|
changePassword()
| seam.user
| update
|
isUserEnabled()
| seam.user
| read
|
grantRole()
| seam.user
| update
|
revokeRole()
| seam.user
| update
|
userExists()
| seam.user
| read
|
listUsers()
| seam.user
| read
|
listRoles()
| seam.role
| read
|
addRoleToGroup()
| seam.role
| update
|
removeRoleFromGroup()
| seam.role
| update
|
The following code listing provides an example set of security rules that grants all
admin role members access to all Identity Management-related methods:
rule ManageUsers no-loop activation-group "permissions" when check: PermissionCheck(name == "seam.user", granted == false) Role(name == "admin") then check.grant(); end rule ManageRoles no-loop activation-group "permissions" when check: PermissionCheck(name == "seam.role", granted == false) Role(name == "admin") then check.grant(); end