属性authenticationDao指定了一个用于从数据库中获取用户信息的Bean。这个属性期望赋予一个org.acegisecurity.providers.dao.DaoAuthenticationProvider的实例。接下来的问题就是该如何配置authenticationDao Bean了。
- <bean id="authenticationDao"
- class="org.acegisecurity.userdetails.memory.InMemoryDaoImpl">
- <property name="userMap">
- <value>
- palmerd=4moreyears,ROLE_PRESIDENT
- bauerj=ineedsleep,ROLE_FIELD_OPS,ROLE_DIRECTOR
- myersn=traitor,disabled,ROLE_CENTRAL_OPS
- value>
- property>
- bean>
属性userMap使用一个org.acegisecurity.userdetails.memory.UserMap对象来定义一组用户名、密码和权限。幸运的是,当装配一个InMemoryDaoImpl时,你不必为配置一个UserMap实例而操心,因为Acegi提供了一个属性编辑器,它能够帮你把一个字符串转化为一个UserMap对象。
userMap字符串的每一行都是一个名字—值对,其中名字是用户名,值是一个由逗号分隔的列表,它以用户密码开头,后面跟着一个或多个赋予该用户的权限的名字(可以将权限看作角色)。
以上的 authenticationDao声明中定义了三个用户:palmerd、bauerj、myersn。这三个用户的密码分别是4moreyears,ineedsleep和traitor。用户palmerd被定义为拥有权限ROLE_PRESIDENT,bauerj被赋予权限ROLE_FIELD_OPS和ROLE_DIRECTOR,并且用户myersn被给予ROLE_CENTRAL_OPS授权。
注意用户myersn的密码后面有disabled这个单词。这是一个特殊的标志,表明该用户已被禁用。
InMemoryDaoImpl有明显的局限性。最主要的一点是,对安全性进行管理时要求你重新编辑Spring的配置文件并且重新部署应用。虽然在开发环境下这是可以接受的(而且可能还是有帮助的),但对于生产用途而言这种做法就太笨拙了。因此,我们强烈反对在生产环境下使用InMemoryDaoImpl,而是应该考虑使用JdbcDaoImpl。
声明一个JDBC DAO
JdbcDaoImpl是一个简单而灵活的认证DAO。以它最简单的形势,只需要一个javax.sql.DataSource对象的引用,可以通过以下方式在Spring配置文件中进行声明:
- <bean id="authenticationDao"
- class="org.acegisecurity.userdetails.jdbc.JdbcDaoImpl">
- <property name="dataSource">
- <ref bean="dataSource"/>
- property>
- bean>
JdbcDaoImpl假设你在数据库中已经建立了某些用于存放用户信息的表。特别地,它假设有一张“Users”表和一张“授权”表,如图:
当JdbcDaoImpl查找用户信息时,它会使用“SELECT username,password,enabled FROM users WHERE username = ?”作为查询语句。类似地,当查找授权时,它会使用“SELECT username,authority FROM authorities WHERE username = ?”。
尽管JdbcDaoImpl假定的表结构非常直接,它们很可能与你已经为应用系统建立的表结构不一致。比如,在Spring培训应用中,Student表保存用户名(在login列中)和密码。是否这意味着你无法在Spring培训应用中使用JdbcDaoImpl来验证学生的身份?
当然不是。但你必须通过设置usersByUserNameQuery属性告诉JdbcDaoImpl如何找到用户信息。下面是对authenticationDao Bean的调整使它更适合Spring培训应用:
- <bean id="authenticationDao"
- class="org.acegisecurity.userdetails.jdbc.JdbcDaoImpl">
- <property name="dataSource">
- <ref bean="dataSource"/>
- property>
- <property name="usersByUserNameQuery">
- <value>
- SELECT login,password FROM student WHERE login=?
- value>
- property>
- bean>
现在JdbcDaoImpl知道如何在Student表中查找用户的认证信息了。但是,还有一件事遗漏了。Student表中没有标志表明用户是否被禁用。事实上,我们一直假设所有的学生都是未被禁用的。但我们如何告诉JdbcDaoImpl做同样的假设?
JdbcDaoImpl还有一个usersByUserNameMapping属性,它引用一个MappingSqlQuery实例。MappingSqlQuery的mapRow()方法将一个ResultSet中的字段映射为一个领域对象。对于JdbcDaoImpl,提供给usersByUserNameMapping属性的MappingSqlQusery对象要求能够将一个ResultSet(通过执行用户查询获得)转化为一个
org.acegisecurity.userdetails.UserDetails对象。
UsersByUserNameMapping显示了一个MappingSqlQuery的实现,它适合将学生用户表的一个查询结果转换为一个UserDetails对象。它从ResultSet中抽取出username和password,但总是设置enabled属性为true。
- public class UsersByUsernameMapping extends MappingSqlQuery{
- protected UsersByUsernameMapping(DataSource dataSource){
- super(dataSource,usersByUsernameQuery);
- declareParameter(new SqlParameter(Types.VARCHAR));
- compile();
- }
- protected Object mapRow(ResultSet rs,int rownum) throws SQLException{
- String username=rs.getString(1);
- String password=rs.getString(2);
-
- UserDetails user=new User(username,password,true,
- new GrantedAuthority[]{new GrantedAuthorityImpl("HOLDER")});
- return user;
- }
- }
剩下唯一需要做的事就是声明一个UsersByUsernameMapping Bean,并将它装配到usersByUserNameMapping属性中。以下的authenticationDao Bean的声明将一个内部Bean装配至usersByUserNameMapping属性中,从而可以应用新的用户映射:
- <bean id="authenticationDao"
- class="org.acegisecurity.userdetails.jdbc.JdbcDaoImpl">
- <property name="dataSource">
- <ref bean="dataSource"/>
- property>
- <property name="usersByUserNameQuery">
- <value>
- SELECT login,password FROM student WHERE login=?
- value>
- property>
- <property name="usersByUserNameMapping">
- <bean class="my.UsersByUsernameMapping"/>
- property>
- bean>
你也能改变 JdbcDaoImpl查询用户权限的方式。与属性usersByUserNameQuery和usersByUserNameMapping定义JdbcDaoImpl如何查询用户认证信息相同,属性authoritiesByUserNameQuery和authoritiesByUserNameMapping告诉JdbcDaoImpl如何查询用户的权限:例如,你可以使用以下代码从user_privileges表中查询已授予一个用户的权限。
- <bean id="authenticationDao"
- class="org.acegisecurity.userdetails.jdbc.JdbcDaoImpl">
- <property name="dataSource">
- <ref bean="dataSource"/>
- property>
- <property name="usersByUserNameQuery">
- <value>
- SELECT login,password FROM student WHERE login=?
- value>
- property>
- <property name="usersByUserNameMapping">
- <bean class="my.UsersByUsernameMapping"/>
- property>
- <property name="authoritiesByUserNameQuery">
- <value>
- SELECT login,privilege FROM user_privileges where login=?
- value>
- property>
- bean>
你可以将属性authoritiesByUserNameMapping设置成一个定制的MappingSqlQuery对象,从而可以定制权限查询的结果如何映射为一个org.acegisecurity.GrantedAuthority对象。但是,由于默认的MappingSqlQuery对上面给出的查询来说已经足够了,我们就不再画蛇添足了。
使用加密的密码
默认地,DaoAuthenticationProvider假设用户的密码是以明文方式(未加密的方式)存储的。但在与数据库中取出的密码进行比较之前,可以使用一个密码编码器加密用户输入的明文密码。下面介绍Acegi提供的三个密码编码器:
1. 默认) 不对密码进行编码,直接返回未经改变的密码;PlaintextPasswordEncoder(
2. 对密码进行消息摘要(MD5)加密;Md5PasswordEncoder
3. 对密码进行安全哈希算法(SHA)编码。ShaPasswordEncoder
你可以通过设置DaoAuthenticationProvider的passwordEncoder属性改变它的密码编码器。例如,要使用MD5编码可以用以下代码:
你也需要设置编码器的种子源(salt source)。一个种子源为编码提供种子(salt),或者称编码的密钥。下面是Acegi提供的两个种子源:
1. 使用用户的User对象中某个指定的属性来获取种子;ReflectionSaltSource
2. 对系统中所有用户使用相同的种子。SystemWideSaltSource
SystemWideSaltSource适用于大多数情形。以下一段XML将一个SystemWideSaltSource装配到DaoAuthenticationProvider的saltSource属性中:
- <property name=saltSource">
- <bean class="org.acegisecurity.providers.dao.salt.SystemWideSaltSource">
- <property name="systemWideSalt">
- <value>123abcvalue>
- property>
- bean>
- property>
(这个种子其实就是在原由的字符串后追加上这个指定的字符串,然后再叫给Md5加密)
ReflectionSaltSource使用用户对象的某个特定属性作为用户密码的编码种子。由于这意味着每个用户的密码都会以不同的方式编码,因此更安全。若要装配一个ReflectionSaltSource,可以通过如下方式将它装配到saltSource属性中:
- <property name=saltSource">
- <bean class="org.acegisecurity.providers.dao.salt.ReflectionSaltSource">
- <property name="userPropertyToUse">
- <value>userNamevalue>
- property>
- bean>
- property>
-
在这里,用户的userName属性被用作种子来加密用户的密码。要特别重视的是必须保证种子是静态的,永远不会改变;否则,一旦种子改变,就再也不可能对用户身份进行验证了,因为MD5是不可逆的。
缓存用户信息
每次当请求一个受保护的资源时,认证管理起就被调用以获取用户的安全信息。但如果获取用户信息涉及到查询数据库,每次都查询相同的数据可能在性能上表现得很糟糕。注意到用户信息不会频繁改变,也许更好的做法是在第一次查询时缓存用户信息,并在后续的查询中直接从缓存中获取用户信息。
DaoAuthenticationProvider通过org.acegisecurity.providers.dao.UserCache接口的实现类支持对用户信息进行缓存。
- public interface UserCache {
- UserDetails getUserFromCache(String username);
- void putUserInCache(UserDetails user);
- void removeUserFromCache(String username);
- }
顾名思义,接口UserCache中方法提供了向缓存中放入、取得和删除用户明细信息的功能。写一个你自己的UserCache实现类是相当简单的。然而,在你考虑开发自己的UserCache实现类之前,应该首先考虑Acegi提供的两个方便的UserCache实现类:
1. org.acegisecurity.providers.dao.cache. NullUserCache
2. org.acegisecurity.providers.dao.cache. EhCacheBasedUserCache
NullUserCache事实上不进行任何缓存。任何时候调用它的getUserFromCache方法,得到的返回值都是null。这是DaoAuthenticationProvider使用的默认UserCache实现。
EhCacheBasedUserCache是一个更实用的缓存实现。类如其名,它是基于开源项目ehcache实现的。Ehcache是一个简单快速的针对Java的缓存解决方案,同时也是Hibernate默认的和推荐的缓存方案。
在DaoAuthenticationProvider中使用ehcache是很简单的,只需要简单地声明一个EhCacheBasedUserCase Bean即可:
- <bean id=userCache"
- class="org.acegisecurity.providers.dao.cache. EhCacheBasedUserCache">
- <property name="minutesToIdle">15property>
- bean>
属性minutesToIdle告诉缓存器一条用户信息在没有访问的情况下应该在缓存中保存多久。这里,我们设定在15分钟的非活动期后删除该条用户信息。
声明了userCache Bean之后,下面唯一要做的事就是把它装配到DaoAuthenticationProvider的userCache属性中:
- <bean id="authenticationProvider"
- class="org.acegisecurity.providers.dao.DaoAuthenticationProvider">
- <property name="userCache">
- <ref bean="userCache"/>
- property>
- bean>
2.3
根据LDAP
仓库进行身份验证(未完,代续)
2.4基于Acegi和Yale CAS实现单次登录(未完,代续)