15.4. 身份管理
身份管理提供了一个Seam应用程序的用户和角色管理的标准API,而且不用理会后端使用的是什么身份存储机制(数据库,LDAP等)。身份管理API的核心是identityManager
组件,它提供了包括创建、修改和删除用户,授权和吊销角色,修改密码,启用和禁用用户账户,验证用户以及列出所有用户和角色的所有方法。
identityManager
必须首先配置一个或多个IdentityStore
。这些组件承担了与后端安全提供者进行交互的实际工作,不管是一个数据库、LDAP服务器还是其他的什么安全提供者。15.4.1. 配置IdentityManager
identityManager
组件允许为验证和授权操作配置为独立的身份存储。这意味着通过一个身份存储(例如一个LDAP目录)进行用户验证,而从另外一个身份存储(例如关系型数据库)取得它的角色。
Seam提供开箱即用两个IdentityStore
实现;JpaIdentityStore
使用关系数据库来存储用户和角色信息,如果在identityManager
组件配置中没有显式说明,那么它是默认的身份存储机制。另外提供的实现是LdapIdentityStore
,它使用一个LDAP目录来存储用户和角色。
identityManager
组件拥有两个配置属性-identityStore
和roleIdentityStore
。这俩个属性的值必须是一个引用实现了IdentityStore
接口的Seam组件的EL表达式。如上所述,如果不配置则假设JpaIdentityStore
为默认值。如果只配置了identityStore
属性,那么roleIdentityStore
属性也使用相同的值。例如以下的在components.xml
中的配置将指定使用LdapIdentityStore
的相关用户和角色的操作:identityManager
配置中,使用LdapIdentityStore
的相关的用户操作,而使用JpaIdentityStore
的相关的角色操作:
identity-store ="#{ldapIdentityStore}"
role-identity-store ="#{jpaIdentityStore}" />
该身份存储允许将用户和角色存储在关系型数据库中。它被设计成尽可能的不受数据库模式设计的限制,在表结构上具有很大的灵活性。这是通过使用一套特别的注释实现的,实体Bean通过配置这些注释而能存储用户和角色纪录。
JpaIdentityStore
需要配置user-class
和role-class
这俩个属性。它们将各自引用用来存储用户和角色纪录的实体类。接下来的例子来自SeamSpace,演示了在components.xml
中的配置:
user-class ="org.jboss.seam.example.seamspace.MemberAccount"
role-class ="org.jboss.seam.example.seamspace.MemberRole" />
如上所述,有一套用来将实体Bean配置成能存储用户和角色的特别注释。下表将详细描述这些注释。
表 15.1. 用户实体注释 注释 | 状态 | 描述 |
---|---|---|
| 必选 | 标明字段或方法包含用户的用户名。 |
| 必选 | 标明字段或方法包含用户的密码。它允许指定一个 @UserPassword(hash = "md5") 如果一个应用需要一个不被Seam原生支持的散列算法,可以通过扩展 |
| 可选 | 标明字段或方法包含用户的姓。 |
| 可选 | 标明字段或方法包含用户的名。 |
| 可选 | 标明字段或方法包含用户的可用状态。它是一个布尔属性,如果没有指定,那么所有用户账户都被假定为可用。 |
| 必选 | 标明字段或方法包含用户的角色。这个属性将在下文进行更详细的描述。 |
注释 | 状态 | 描述 |
---|---|---|
| 必选 | 标明字段或方法包含角色的名称。 |
| 可选 | 标明字段或方法包含角色的组关系。 |
| 可选 | 标明字段或方法说明角色是否有依赖性。依赖性角色将在后文中解释。 |
如前所述,JpaIdentityStore
被设计成尽可能的灵活,以应对你的数据库模式中的用户和角色的表设计。在本节中,将看到一些可能被用到的存储用户和角色纪录的数据库模式。
UserRoles
的交叉引用表实现了多对多的关系。
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; }
}
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; }
}
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; }
}
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; }
}
在IdentityManager
中使用JpaIdentityStore
作为身份管理的实现时,调用某些IdentityManager
方法会触发一些事件。
15.4.2.4.1. JpaIdentityStore.EVENT_PRE_PERSIST_USER
调用IdentityManager.createUser()
时会相应触发该事件。在用户实体被持久化到数据库之前,该事件被触发并传递实体实例作为事件参数。这个实体是配置文件中JpaIdentityStore
指定的user-class
的实例。
为这个事件编写一个观察者对于在这个实体上设置在标准createUser()
方法中不能设置的额外的字段值是很有用的。
调用IdentityManager.createUser()
时也会相应触发该事件。然而,它是在用户实体已经持久化到数据库后触发的。就像EVENT_PRE_PERSIST_USER
事件,它也传递这个实体实例作为事件参数。如果你需要持久化用户实体引用的其他实体的话,例如联系人详细纪录或者其他用户指定的数据,可以为这个事件设置一个观察者来实现。
IdentityManager.authenticate()
时会相应触发该事件。它传递用户实体实例作为事件参数,这样可以帮助你从被验证的用户实体中读取其他额外的属性。
这个身份存储是专为存储在一个LDAP目录中的用户纪录来设计的。它具有高配置化,以及对存储在LDAP目录中用户和角色的设计具有极大的灵活性。以下的章节将描述这个身份存储的配置选项以及提供了一些配置示例。
下表描述了在components.xml
中配置LdapIdentityStore
可能用到的属性。
Table 15.3. LdapIdentityStore配置属性
属性 | 缺省值 | 描述 |
---|---|---|
|
| LDAP服务器地址。 |
|
| LDAP服务器监听端口号。 |
|
| 包含用户纪录的上下文的标识名(DN)。 |
|
| 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. |
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.
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"
/>
编写你自己的身份存储实现允许你验证和执行不被Seam支持的其他安全提供者的身份管理操作。只需要一个简单的就能达到这个目标,它必须实现org.jboss.seam.security.management.IdentityStore
接口。
必须实现的方法请参考IdentityStore的JavaDoc。
如果你正在你的Seam应用中使用身份管理,那么不需要提供一个验证器组件(参考前面的验证章节)来进行验证。简单的忽略components.xml
文件中的identity
配置的authenticator-method
,并且SeamLoginModule
将默认使用IdentityManager
来验证你的应用的用户,而不需要特别的配置。
instance()
获得:
下表描述了IdentityManager
的方法:
方法 | 返回值 | 描述 |
---|---|---|
| | 用指定的用户名和密码创建一个用户账户。如果成功返回 |
| | 删除一个指定用户名的用户账户。如果成功返回 |
| | 使用指定的名称创建一个角色。如果成功返回 |
| | 删除一个指定名称的角色。如果成功返回 |
| | 启用一个指定名称的用户账户。不能验证的账户不能启用。如果成功返回 |
| | 禁用一个指定名称的用户账户。如果成功返回 |
| | 修改指定用户名的账户的密码。如果成功返回 |
| | 如果账户是启用的返回 |
| | 为指定的用户或角色赋予指定的角色。角色必须是已经存在的。如果成功授权返回 |
| | 撤销从指定的用户或角色的特定角色。如果指定用户是角色的成员并且成功撤销,返回 |
| | 如果存在指定的用户返回 |
| | 按照字母序返回用户用户名的列表。 |
| | 按照字母序返回使用指定的过滤参数过滤的用户用户名的列表。 |
| | 返回所有角色名称的列表。 |
| | 返回一个所有角色的名字明确授予指定的用户名的列表。 |
| | 返回一个所有角色的名字隐式授予指定的用户名列表。隐式授予角色包括那些不直接授予一个用户,而他们给予该用户的角色是其成员。例如,管理员角色是用户的角色的成员,而用户是管理员角色的成员,那么该用户的暗示作用都是管理员和用户角色。 |
| | 验证指定的用户名和密码存储配置的身份。如果成功返回 |
| | 添加作为指定组的成员中指定的角色。如果操作成功返回true。 |
| | 从指定的角色组中删除指定的角色。如果操作成功返回true。 |
| | 列出所有关角色的名字。 |
使用这些身份管理API需要调用用户有合适的授权才能调用这些方法。下表描述了IdentityManager
中每一个方法需要的权限。表中列出的权限目标是字符串值。
方法 | 权限目标 | 权限动作 |
---|---|---|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
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