Acegi(02): Acegi管理身份验证

2. 管理身份验证
决定是否允许用户访问受保护资源的的第一步是判断用户的身份。在大多数应用系统中,这意味着用户在一个登录界面上提供用户名和密码。用户名告诉应用系统用户声明自己是谁。为了确保用户的身份,用户需要同时提供一个密码。如果应用系统的安全机制确认密码是正确的,则系统假设用户的实际身份与他声明的身份相同。
在 Acegi中,是由认证管理器负责确定用户身份的。一个认证管理器由接口
  1. public interface AuthenticationManager {       
  2.     Authentication authenticate(Authentication authentication)   
  3.         throws AuthenticationException;   
  4. }  
认证管理器的 authenticate()方法需要一个org.acegisecurity.Authentication对象(其中可能只包括用户名和密码)作为参数,它会尝试验证用户身份。如果认证成功,authenticate()方法返回一个Authentication对象,其中包括用户已被授权的权限(将由授权管理器使用)。如果认证失败,则它会抛出一个AuthenticationException 。
 
2.1 配置ProviderManager
    ProviderManager是认证管理器的一个实现,它将验证身份的责任委托给一个或多个认证提供者。
ProviderManager:
DaoAuthenticationProvider
DaoAuthenticationProvider
JaasAuthenticationProvider
CasAuthenticationProvider
RemoteAuthenticationProvider
ProviderManager的思路是使你能够根据多个身份管理源来认证用户。它不是依靠自己实现身份验证,而是逐一遍历一个认证提供者的集合,直到某一个认证提供者能够成功地验证该用户的身份(或者已经尝试完了该集合中所有的认证提供者)。
你可以在 Spring配置文件中按如下方式配置一个ProviderManager:
<bean id="authenticationManager" div=""></bean>
   
  1. <bean id="authenticationManager"  
  2.     class="org.acegisecurity.providers.ProviderManager">  
  3.     <property name="providers">  
  4.     <list>  
  5.         <ref bean="daoAuthenticationProvider"/>  
  6.         <ref bean="jaasAuthenticationProvider"/>  
  7.     list>  
  8.     property>  
  9. bean>  
   
通过 providers属性可以为ProviderManager提供一个认证提供者的列表。通常你只需要一个认证提供者,但在某些情况下,提供由若干个认证提供者组成的列表是有用的。在这种情况下,如果一个认证提供者验证身份失败,可以尝试另一个认证提供者。一个认证提供者是由org.acegisecurity.providers.AuthenticationProvider接口定义的。Spring提供了若干个AuthenticationProvider的有用实现,如下所示:
认证提供者
目的
org.acegisecurity.adapters.
AuthByAdapterProvide
使用容器的适配器验证身份
org.acegisecurity.providers.cas.
CasAuthenticationProvider
根据 Yale中心认证服务验证身份
org.acegisecurity.providers.dao.
DaoAuthenticationProvider
从数据库中获取用户信息,包括用户名和密码
org.acegisecurity.providers.jaas.
JaasAuthenticationProvider
从 JAAS登录配置中获取用户信息
org.acegisecurity.providers.rcp.
RemoteAuthenticationProvider
根据远程服务验证用户身份
org.acegisecurity.runas. RunAsImplAuthenticationProvider
针对身份已经被运行身份管理器替换的用户进行认证
org.acegisecurity.providers. TestingAuthenticationProvider
用于单元测试。自动认为一个 TestingAuthenticationToken是有效的。不应用于生产环境。
 
你可以认为一个 AuthenticationProvider是一个下属的AuthenticationManager。事实上,AuthenticationProvider接口也有一个authenticate()方法,该方法的签名与AuthenticationManager的authenticate()方法完全一样。
下面我们关注三个最常用的认证提供者。首先从使用 DaoAuthenticationProvider进行简单的基于数据库验证身份开始。
2.2 根据数据库验证身份
    大多数应用系统将包括用户名和密码在内的用户信息保存在数据库中。如果这和你的情况相符,那么你可以使用 DaoAuthenticationProvider。
    声明一个DAO认证提供者
    一个 DaoAuthenticationProvider是一个简单的认证提供者,它使用DAO来从数据库中获取用户信息(包括用户的密码)。
    取得了用户名和密码之后, DaoAuthenticationProvider通过比较从数据库中获取的用户名和密码以及来自认证管理器的通过Authentication对象中传入的用户名和密码完成身份验证。如果用户名和密码与数据库中的信息匹配,则用户通过身份验证,同时返回给认证管理器一个已完全填充的Authentication对象。否则会抛出一个AuthenticationException,表明身份验证失败。
 
配置一个DaoAuthenticationProvider在简单不过了。下一段XML摘要显示了如何声明一个DaoAuthenticationProvider Bean,并且装配上它所依赖的DAO。     
  1. <bean id="authenticationProvider"  
  2.     class="org.acegisecurity.providers.dao.DaoAuthenticationProvider">  
  3.     <property name="authenticationDao">  
  4.         <ref bean="authenticationDao"/>  
  5.     property>  
  6. bean>  
      
属性authenticationDao指定了一个用于从数据库中获取用户信息的Bean。这个属性期望赋予一个org.acegisecurity.providers.dao.DaoAuthenticationProvider的实例。接下来的问题就是该如何配置authenticationDao Bean了。
       Acegi提供了两个可供选择的AuthenticationDao的实例:InMemoryDaoImpl和JdbcDaoImpl。我们首先配置一个InMemoryDaoImpl作为authenticationDao Bean的实例,然后再使用更实用的JdbcDaoImpl替换它。
使用内存DAO
       尽管假定AuthenticationDao对象总是通过查询关系数据库获取用户信息是一种自然的想法,事实情形却不必总是如此。如果你的应用系统的身份验证需求是微不足道,或者是为了开发期间方便起见,也许更简单的做法是在Spring配置文件中直接配置你的用户信息。
       为此,Acegi提供了InMemoryDaoImpl,一个从Spring配置文件中获取用户信息的AuthenticationDao。你能够在Spring配置文件中通过以下方法配置一个InMemoryDaoImpl:
<bean id="authenticationDao" div=""></bean>
      
  1. <bean id="authenticationDao"  
  2.     class="org.acegisecurity.userdetails.memory.InMemoryDaoImpl">  
  3.     <property name="userMap">  
  4.         <value>  
  5.             palmerd=4moreyears,ROLE_PRESIDENT   
  6.             bauerj=ineedsleep,ROLE_FIELD_OPS,ROLE_DIRECTOR   
  7.             myersn=traitor,disabled,ROLE_CENTRAL_OPS   
  8.         value>  
  9.     property>  
  10. 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配置文件中进行声明:
  1. <bean id="authenticationDao"  
  2.        class="org.acegisecurity.userdetails.jdbc.JdbcDaoImpl">  
  3.        <property name="dataSource">  
  4.         <ref bean="dataSource"/>  
  5.        property>  
  6. 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培训应用:
  1. <bean id="authenticationDao"  
  2.        class="org.acegisecurity.userdetails.jdbc.JdbcDaoImpl">  
  3.        <property name="dataSource">  
  4.         <ref bean="dataSource"/>  
  5.        property>  
  6.        <property name="usersByUserNameQuery">  
  7.         <value>  
  8.               SELECT login,password FROM student WHERE login=?   
  9.         value>  
  10.        property>  
  11. 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。
  1. public class UsersByUsernameMapping extends MappingSqlQuery{   
  2.        protected UsersByUsernameMapping(DataSource dataSource){   
  3.               super(dataSource,usersByUsernameQuery);   
  4.               declareParameter(new SqlParameter(Types.VARCHAR));   
  5.               compile();   
  6.        }   
  7.        protected Object mapRow(ResultSet rs,int rownum) throws SQLException{   
  8.               String username=rs.getString(1);   
  9.               String password=rs.getString(2);   
  10.     
  11.               UserDetails user=new User(username,password,true,   
  12.                      new GrantedAuthority[]{new GrantedAuthorityImpl("HOLDER")});   
  13.                return user;   
  14.        }   
  15. }   
 
剩下唯一需要做的事就是声明一个UsersByUsernameMapping Bean,并将它装配到usersByUserNameMapping属性中。以下的authenticationDao Bean的声明将一个内部Bean装配至usersByUserNameMapping属性中,从而可以应用新的用户映射:
  1. <bean id="authenticationDao"  
  2.     class="org.acegisecurity.userdetails.jdbc.JdbcDaoImpl">  
  3.     <property name="dataSource">  
  4.       <ref bean="dataSource"/>  
  5.     </property>  
  6.     <property name="usersByUserNameQuery">  
  7.       <value>  
  8.         SELECT login,password FROM student WHERE login=?   
  9.       </value>  
  10.     </property>  
  11.     <property name="usersByUserNameMapping">  
  12.         <bean class="my.UsersByUsernameMapping"/>          
  13.     </property>  
  14. </bean>  
你也能改变 JdbcDaoImpl查询用户权限的方式。与属性usersByUserNameQuery和usersByUserNameMapping定义JdbcDaoImpl如何查询用户认证信息相同,属性authoritiesByUserNameQuery和authoritiesByUserNameMapping告诉JdbcDaoImpl如何查询用户的权限:例如,你可以使用以下代码从user_privileges表中查询已授予一个用户的权限。
  1. <bean id="authenticationDao"     
  2.     class="org.acegisecurity.userdetails.jdbc.JdbcDaoImpl">     
  3.     <property name="dataSource">     
  4.       <ref bean="dataSource"/>     
  5.     </property>     
  6.     <property name="usersByUserNameQuery">     
  7.       <value>     
  8.         SELECT login,password FROM student WHERE login=?      
  9.       </value>     
  10.     </property>     
  11.     <property name="usersByUserNameMapping">     
  12.         <bean class="my.UsersByUsernameMapping"/>             
  13.     </property>     
  14.     <property name="authoritiesByUserNameQuery">     
  15.        <value>     
  16.         SELECT login,privilege FROM user_privileges where login=?      
  17.        </value>     
  18.     </property>     
  19. </bean>     
你可以将属性authoritiesByUserNameMapping设置成一个定制的MappingSqlQuery对象,从而可以定制权限查询的结果如何映射为一个org.acegisecurity.GrantedAuthority对象。但是,由于默认的MappingSqlQuery对上面给出的查询来说已经足够了,我们就不再画蛇添足了。
 
使用加密的密码
       默认地,DaoAuthenticationProvider假设用户的密码是以明文方式(未加密的方式)存储的。但在与数据库中取出的密码进行比较之前,可以使用一个密码编码器加密用户输入的明文密码。下面介绍Acegi提供的三个密码编码器:
1.         默认) 不对密码进行编码,直接返回未经改变的密码;PlaintextPasswordEncoder(
2.         对密码进行消息摘要(MD5)加密;Md5PasswordEncoder 
3.         对密码进行安全哈希算法(SHA)编码。ShaPasswordEncoder   
你可以通过设置DaoAuthenticationProvider的passwordEncoder属性改变它的密码编码器。例如,要使用MD5编码可以用以下代码:
<property name="passwordEncoder&quot;"></property>
       <bean class="org.acegisecurity.providers.encoding.Md5PasswordEncoder"></bean>
你也需要设置编码器的种子源(salt source)。一个种子源为编码提供种子(salt),或者称编码的密钥。下面是Acegi提供的两个种子源:
1.         使用用户的User对象中某个指定的属性来获取种子;ReflectionSaltSource 
2.         对系统中所有用户使用相同的种子。SystemWideSaltSource
SystemWideSaltSource适用于大多数情形。以下一段XML将一个SystemWideSaltSource装配到DaoAuthenticationProvider的saltSource属性中:
  1. <property name=saltSource">  
  2.        <bean class="org.acegisecurity.providers.dao.salt.SystemWideSaltSource">  
  3.               <property name="systemWideSalt">  
  4.                      <value>123abcvalue>  
  5.               property>  
  6.        bean>  
  7. property>  
(这个种子其实就是在原由的字符串后追加上这个指定的字符串,然后再叫给Md5加密)
ReflectionSaltSource使用用户对象的某个特定属性作为用户密码的编码种子。由于这意味着每个用户的密码都会以不同的方式编码,因此更安全。若要装配一个ReflectionSaltSource,可以通过如下方式将它装配到saltSource属性中: 
  1. <property name=saltSource">  
  2.        <bean class="org.acegisecurity.providers.dao.salt.ReflectionSaltSource">  
  3.               <property name="userPropertyToUse">  
  4.                      <value>userNamevalue>  
  5.               property>  
  6.        bean>  
  7. property>  
  8.    
在这里,用户的userName属性被用作种子来加密用户的密码。要特别重视的是必须保证种子是静态的,永远不会改变;否则,一旦种子改变,就再也不可能对用户身份进行验证了,因为MD5是不可逆的。
缓存用户信息
每次当请求一个受保护的资源时,认证管理起就被调用以获取用户的安全信息。但如果获取用户信息涉及到查询数据库,每次都查询相同的数据可能在性能上表现得很糟糕。注意到用户信息不会频繁改变,也许更好的做法是在第一次查询时缓存用户信息,并在后续的查询中直接从缓存中获取用户信息。
       DaoAuthenticationProvider通过org.acegisecurity.providers.dao.UserCache接口的实现类支持对用户信息进行缓存。 
  1. public interface UserCache {   
  2.     UserDetails getUserFromCache(String username);   
  3.     void putUserInCache(UserDetails user);   
  4.     void removeUserFromCache(String username);   
  5. }  
 
顾名思义,接口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即可:
  1. <bean id=userCache"   
  2.        class="org.acegisecurity.providers.dao.cache. EhCacheBasedUserCache">  
  3.        <property name="minutesToIdle">15property>  
  4. bean>  
属性minutesToIdle告诉缓存器一条用户信息在没有访问的情况下应该在缓存中保存多久。这里,我们设定在15分钟的非活动期后删除该条用户信息。
声明了userCache Bean之后,下面唯一要做的事就是把它装配到DaoAuthenticationProvider的userCache属性中:
  1. <bean id="authenticationProvider"  
  2.        class="org.acegisecurity.providers.dao.DaoAuthenticationProvider">  
  3.        <property name="userCache">  
  4.               <ref bean="userCache"/>  
  5.        property>  
  6. bean>  
2.3 根据LDAP 仓库进行身份验证(未完,代续)
2.4基于AcegiYale CAS实现单次登录(未完,代续)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值