实际企业应用中,用户密码一般都会进行加密处理,这样才能使企业应用更加安全。既然密码的加密如此之重要,那么Acegi(Spring Security)作为成熟的安全框架,当然也我们提供了相应的处理方式。
针对用户密码的加密工作,DaoAuthenticationProvider同时暴露了passwordEncoder和saltSource属性。PasswordEncoder和SaltSource是可选的属性,PasswordEncoder负责对认证库中的密码进行加解密。而SaltSource则是在产生密码时给它加点“盐”,以增强密码在认证库中的安全性。Acegi安全系统提供的PasswordEncoder实现中包括MD5、SHA和明文编码。
Acegi提供了三种加密器:
PlaintextPasswordEncoder---默认,不加密,返回明文.
ShaPasswordEncoder---哈希算法(SHA)加密
Md5PasswordEncoder---消息摘要(MD5)加密
Acegi安全系统提供了两个SaltSource的实现:
SystemWideSaltSource,它用同样的"盐"对系统中所有的密码进行编码
ReflectionSaltSource只对从UserDetails对象返回的获得这种"盐"的指定属性进行检查。请参考JavaDoc以获取对这些可选特性的更详细信息。
图1为Acegi内置的PasswordEncoder继承链。默认时,DaoAuthenticationProvider会实例化PlaintextPasswordEncoder对象,即应对用户密码未进行加密处理的情况。
图2为Acegi内置的SaltSource继承链。默认时,SaltSource的取值为null,即未启用密码私钥。
1、 PlaintextPasswordEncoder明文密码
在使用明文密码期间,如果系统允许登录用户不区分密码大小写,则应设置ignorePasswordCase属性值为true。
在Acegi安全配置文件中添加:
<bean id="daoAuthenticationProvider" class="org.acegisecurity.providers.dao.DaoAuthenticationProvider"> <property name="userDetailsService" ref="inMemDaoImpl" /> <!-- 增加 --> <property name="passwordEncoder" ref="plaintextPasswordEncoder" /> </bean> <!-- 增加 --> <bean id="plaintextPasswordEncoder" class="org.acegisecurity.providers.encoding.PlaintextPasswordEncoder"> <property name="ignorePasswordCase" value="true"></property> </bean>
2、 ShaPasswordEncoder哈希算法(SHA)加密
在Acegi安全配置文件中添加:
<bean id="daoAuthenticationProvider" class="org.acegisecurity.providers.dao.DaoAuthenticationProvider"> <property name="userDetailsService" ref="inMemDaoImpl" /> <!-- 增加 --> <property name="passwordEncoder" ref="shaPasswordEncoder" /> <!-- <property name="passwordEncoder" ref="shaMessageDigestPasswordEncoder" /> --> </bean> <!-- 增加, 以下两种配置方式等效 --> <bean id="shaPasswordEncoder" class="org.acegisecurity.providers.encoding.ShaPasswordEncoder"> <constructor-arg> <value>256</value> </constructor-arg> <property name="encodeHashAsBase64" value="false"></property> </bean> <!-- <bean id="shaMessageDigestPasswordEncoder" class="org.acegisecurity.providers.encoding.MessageDigestPasswordEncoder"> <constructor-arg> <value>SHA-256</value> </constructor-arg> <property name="encodeHashAsBase64" value="false"></property> </bean> -->
3、 Md5PasswordEncoder消息摘要(MD5)加密
在Acegi安全配置文件中添加:
<bean id="daoAuthenticationProvider" class="org.acegisecurity.providers.dao.DaoAuthenticationProvider"> <property name="userDetailsService" ref="inMemDaoImpl" /> <!-- 增加 --> <property name="passwordEncoder" ref="md5PasswordEncoder" /> <!-- <property name="passwordEncoder" ref="md5MessageDigestPasswordEncoder" /> --> </bean> <!-- 增加. 以下两种配法等效 --> <bean id="md5PasswordEncoder" class="org.acegisecurity.providers.encoding.Md5PasswordEncoder"> <property name="encodeHashAsBase64" value="false"></property> </bean> <!-- <bean id="md5MessageDigestPasswordEncoder" class="org.acegisecurity.providers.encoding.MessageDigestPasswordEncoder"> <constructor-arg> <value>MD5</value> </constructor-arg> <property name="encodeHashAsBase64" value="false"></property> </bean> -->
4、 SystemWideSaltSource
SystemWideSaltSource会采用一个静态字符串表示密码私钥(salt),所有用户的密码处理都会采用这一私钥。与未启用密码私钥相比,SystemWideSaltSource更为安全,因为它使得密码的破解变得更困难。默认时,它会采用“密码{密码私钥}”形式加密密码。
配置如下:
<bean id="daoAuthenticationProvider" class="org.acegisecurity.providers.dao.DaoAuthenticationProvider"> <property name="userDetailsService" ref="userDetailsService" /> <property name="passwordEncoder" ref="md5PasswordEncoder" /><!-- 增加 --> <!-- 增加 --> <property name="saltSource"> <bean class="org.acegisecurity.providers.dao.salt.SystemWideSaltSource"> <property name="systemWideSalt" value="javaee"></property> </bean> </property> </bean>
5、 ReflectionSaltSource
尽管采用SystemWideSaltSource能够加强密码的保护,但由于SystemWideSaltSource采用了全局性质的密码私钥,因此仍存在一定的缺陷。
而ReflectionSaltSource采用了个案性质的密码私钥,即其密码加密所采用的私钥是动态变化的,因此它更为安全。ReflectionSaltSource仍采用“密码{密码私钥}”的形式加密密码。
配置如下:
<bean id="daoAuthenticationProvider" class="org.acegisecurity.providers.dao.DaoAuthenticationProvider"> <property name="userDetailsService" ref="userDetailsService" /> <property name="passwordEncoder" ref="md5PasswordEncoder" /> <!-- 增加 --> <property name="saltSource"> <bean class="org.acegisecurity.providers.dao.salt.ReflectionSaltSource"> <property name="userPropertyToUse" value="getUsername"></property> </bean> </property> </bean>
此时,ReflectionSaltSource将调用UserDetails对象的getUsername()方法获得各用户的密码私钥。比如:javaee(用户名)/password(密码) 用户的密码将被以“password{javaee}”的形式进行加密处理。
通过指定userPropertyToUse属性的值,开发者能够控制密码私钥的来源。比如上述的getUsername的含义就是动态调用相应的getUsername()方法,并将返回结果直接作为密码的私钥。在实际企业应用中,Acegi应用开发者可能会提供自身的UserDetails实现类,这时userPropertyToUse属性的取值范围更加广泛。
6、 在web.xml中,提供了三种方式方便大家调试
<!-- <context-param> <param-name>contextConfigLocation</param-name> <param-value> /WEB-INF/applicationContext-acegi-security-plaintext.xml </param-value> </context-param> --> <!-- <context-param> <param-name>contextConfigLocation</param-name> <param-value> /WEB-INF/applicationContext-acegi-security-sha.xml </param-value> </context-param> --> <context-param> <param-name>contextConfigLocation</param-name> <param-value> /WEB-INF/applicationContext-acegi-security-md5.xml </param-value> </context-param> <!-- <context-param> <param-name>contextConfigLocation</param-name> <param-value> /WEB-INF/applicationContext-acegi-security-md5-salt.xml </param-value> </context-param> -->
7、 这里提供了一个用于生成加密密码的类
package org.acegi.sample; import org.acegisecurity.providers.encoding.Md5PasswordEncoder; import org.acegisecurity.providers.encoding.MessageDigestPasswordEncoder; import org.acegisecurity.providers.encoding.ShaPasswordEncoder; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; /** * 加密管理类 * * @author zhanjia * */ public class PasswordProcessing { private static final Log log = LogFactory.getLog(PasswordProcessing.class); public static void processMd5() { log.info("以MD5方式加密......................"); // 直接指定待采用的加密算法(MD5) MessageDigestPasswordEncoder mdpeMd5 = new MessageDigestPasswordEncoder("MD5"); // 生成32位的Hex版, 这也是encodeHashAsBase64的默认值 mdpeMd5.setEncodeHashAsBase64(false); log.info(mdpeMd5.encodePassword("password", null)); // 生成24位的Base64版 mdpeMd5.setEncodeHashAsBase64(true); log.info(mdpeMd5.encodePassword("password", null)); // 等效于上述代码 Md5PasswordEncoder mpe = new Md5PasswordEncoder(); mpe.setEncodeHashAsBase64(false); log.info(mpe.encodePassword("password", null)); mpe.setEncodeHashAsBase64(true); log.info(mpe.encodePassword("password", null)); } public static void processSha() { log.info("以SHA方式加密......................"); // 直接指定待采用的加密算法(SHA)及加密强度(256) MessageDigestPasswordEncoder mdpeSha = new MessageDigestPasswordEncoder("SHA-256"); mdpeSha.setEncodeHashAsBase64(false); log.info(mdpeSha.encodePassword("password", null)); mdpeSha.setEncodeHashAsBase64(true); log.info(mdpeSha.encodePassword("password", null)); // 等效于上述代码 ShaPasswordEncoder spe = new ShaPasswordEncoder(256); spe.setEncodeHashAsBase64(false); log.info(spe.encodePassword("password", null)); spe.setEncodeHashAsBase64(true); log.info(spe.encodePassword("password", null)); } public static void processSalt() { log.info("以MD5方式加密、加私钥(盐)......................"); Md5PasswordEncoder mpe = new Md5PasswordEncoder(); mpe.setEncodeHashAsBase64(false); // 等效的两行地代码 log.info(mpe.encodePassword("password{javaee}", null)); // javaee为密码私钥 log.info(mpe.encodePassword("password", "javaee")); // javaee为密码私钥 // 结果:87ce7b25b469025af0d5c6752038fb56 } /** * @param args */ public static void main(String[] args) { processMd5(); processSha(); processSalt(); } }