【IT168 专稿】概述
在《使用Acegi保护您的应用(之一)》的文章中,我们讲解了如何将Acegi集成到基于Spring的Web应用中,并介绍了基于Acegi身份认证管理的基本处理。在这篇文章里,我们将继续Acegi身份认证的高级部分。此外,我们还将讲解实施高级身份认证的具体做法。
密码加密的问题
不管是使用InMemoryDaoImpl还是JdbcDaoImpl,它们的工作是根据待认证用户名获取UserDetails对象。在获取UserDetails后,DaoAuthenticationProvider要做的工作是比较Authentication和UserDetails的匹配关系给出认证成功还是失败的认证结果。
但是DaoAuthenticationProvider根据什么规则判断Authentication和UserDetails的匹配关系呢?简单来说,就是判断以下两者的关系:
Authentication#getCredentials() 是否匹配于UserDetails#getPassword()
如果数据库或内存中保存的用户密码是明文的,那么这一比较过程再简单不过了。但是如果用户密码是加密的,也即UserDetails#getPassword()是加密的密码,由于用户提供的密码(Authentication#getCredentials())是用户的直接输入值,它是非加密的,这时如何判断Authentication#getCredentials()的正确性呢?
这就引出了本节所要介绍的两个关键接口:org.acegisecurity.providers.encoding. PasswordEncoder和org.acegisecurity.providers.dao.SaltSource。PasswordEncoder完成两件工作:
1) 对明文的密码(Authentication#getCredentials())进行编码,得到对应的加密值;
2) 对处于非对称状态(一个是加密的,另一个是明文的)两个密码进行比较并给出判断结果。
PasswordEncoder进行密码比较时,需要使用到一个SaltSource,它代表一个“加密盐”(是专业术语,但称之为加密种子更容易理解一些),对用户提供的密码进行加密时采用的加密盐必须和系统中保存的的用户加密密码所采用加密盐相同。
PasswordEncoder定义了两个接口方法:
l String encodePassword(String rawPass, Object salt):对原始未加密的密码通过一定的算法进行加密运算,salt为加密时所用的加密盐。一般使用的是MD5或SHA摘要算法,因为摘要算法有两个特性:1)两个相同的字符串摘要值是相同的;2)通过摘要值不能反推出原来的字符串。这两个特性使得以摘要的方式保存密码是安全的——你无法通过密码摘要获取原始密码。而加密盐使密码安全系数进一步提高:对于相同的密码,只要加密盐不相同,计算出的摘要也是不同的。这里的rawPass是指Authentication#getCredentials().toString();
l boolean isPasswordValid(String encPass, String rawPass, Object salt):通过算法判断待认证用户所提供的密码是否是有效的。在计算过程中,需要对rawPass使用salt加密盐进行加密运算以得到加密值,再同encPass比较就可以得出判断结果。这里,encPass是指UserDetails#getPassword(),而rawPass是指Authentication#getCredentials().toString()。
Acegi在org.acegisecurity.providers.encoding包中提供了几个常见的PasswordEncoder实现类,在表 2中介绍:
表 2 密码编码器
SaltSource接口仅有一个Object getSalt(UserDetails user)方法,Acegi为其提供了两个实现类,它们分别是:
l org.acegisecurity.providers.dao.salt.ReflectionSaltSource:它允许你在UserDetails中提供一个代表加密盐的属性,属性名通过userPropertyToUse指定。ReflectionSaltSource通过反射机制获取UserDetails中的加密盐,所以该实现类允许不同的用户采用不同的加密盐;
l org.acegisecurity.providers.dao.salt.SystemWideSaltSource:该实现类不允许不同用户采用各自的加密盐,它采用全局范围统一的加密盐。你可以通过systemWideSalt指定加密盐的值。
假设,我们保存在T_USER表中的password字段采用了MD5加密并且统一采用全局唯一的加密盐。这时,我们就有必要调整代码清单 7中daoAuthenticationProvider的配置以应用密码编码器:
代码清单 8 applicationContext-acegi-plugin.xml
使用密码编码器
… <bean id="daoAuthenticationProvider" class="org.acegisecurity.providers.dao.DaoAuthenticationProvider"> <property name="userDetailsService" ref="userDetailsService" /> <property name="passwordEncoder"> ①采用MD5加密的密码编码器 <bean class="org.acegisecurity.providers.encoding.Md5PasswordEncoder" /> </property> <property name="saltSource"> ②采用全局统一的加密盐 <bean class="org.acegisecurity.providers.dao.salt.SystemWideSaltSource"> <property name="systemWideSalt" value="baobaotao"/> </bean> </property> </bean> …