在第一篇文章,我们展示了一个demo,其中讲到了对用户的密码进行了明文展示的用法,其实那么做是不安全的,在实际项目中往往会采用各种加密方法(比如:bcrypt,md5,sha1,sha2等)来实现对密码的保护。
本片文章将会主要讲解如何在Spring Security实现对密码加密的各种用法,以及对BCrypt的用法进一步分析。
概念
Spring Security 为我们提供了一套加密规则和密码比对规则,org.springframework.security.crypto.password.PasswordEncoder 接口,该接口里面定义了三个方法。
public interface PasswordEncoder { //加密(外面调用一般在注册的时候加密前端传过来的密码保存进数据库) String encode(CharSequence rawPassword); //加密前后对比(一般用来比对前端提交过来的密码和数据库存储密码, 也就是明文和密文的对比) boolean matches(CharSequence rawPassword, String encodedPassword); //是否需要再次进行编码, 默认不需要 default boolean upgradeEncoding(String encodedPassword) { return false; } }
在Spring Security下 PasswordEncoder 的实现类包含:
其中常用到的分别有下面这么几个:
BCryptPasswordEncoder:Spring Security 推荐使用的,使用BCrypt强哈希方法来加密。
MessageDigestPasswordEncoder:用作传统的加密方式加密(支持 MD5、SHA-1、SHA-256...)
DelegatingPasswordEncoder:最常用的,根据加密类型id进行不同方式的加密,兼容性强
NoOpPasswordEncoder:明文, 不做加密
其他
Spring Security中加密的用法:
使用bcrypt bean
applicationContext-shiro.xml中配置:
<bean id="secureRandom" class="java.security.SecureRandom"/> <bean id="bCryptPasswordEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"> <constructor-arg name="version" value="$2A" /> <!-- salt随机生成版本 默认$2A--> <constructor-arg name="strength" value="10"/> <!-- 使用salt进行加密迭代次数,默认10--> <constructor-arg name="random" ref="secureRandom"/> <!-- 随机算法 --> </bean> <security:authentication-manager> <security:authentication-provider> <security:user-service> <security:user name="user" password="$2a$10$LCe6jsoHUrEvWI1KURrqbu/xfuPU5aZj2RkPTVS0d7MUJiT55Lt/y" authorities="ROLE_USER"/> <security:user name="admin" password="$2a$10$BR3Np37NbmtWHqpSZE6AMeCMG4Rm.UOUEZ3dYrW3oUXHNuSBXjDwi" authorities="ROLE_USER, ROLE_ADMIN"/> </security:user-service> <security:password-encoder ref="bCryptPasswordEncoder"/> </security:authentication-provider> </security:authentication-manager>
说明:
1)需要配置 bCryptPasswordEncoder的bean,在该bean配置时,可以指定其构造函数相关参数:
version:salt随机生成版本,默认:采用 BCryptVersion.$2A.getVersion();
strength:使用salt进行加密迭代次数,默认:10;
random:随机算法,默认:new SecureRandom()。
2)需要在<authentication-provider>标签下的<password-encoder ref=''/>指定该bean。
密码加密用法:
// BCrypt加密与验证,内部默认: PasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); System.out.println("passwordEncoder 123456:" + passwordEncoder.encode("123456")); System.out.println("passwordEncoder 123456:" + passwordEncoder.encode("123456")); // BCrypt密文解析 //参数解释 //1)2a:加密算法版本号。 //2)10:加密轮次,默认为10,数值越大,加密时间和越难破解呈指数增长。可在BCryptPasswordEncoder构造参数传入。 //3)密码加密:前面的内容是盐,后面的内容才是真正的密文。 //以下方式可以更清晰的看出盐和全文。 String salt = BCrypt.gensalt(BCryptPasswordEncoder.BCryptVersion.$2A.getVersion(), 10, new SecureRandom()); String result = BCrypt.hashpw("123456", salt);//全文 System.out.println("salt:" + salt + ",salt's length:" + salt.length()); // salt长度是29 System.out.println("result:" + result);
在对密码加密时,可以采用上边这3种方法:
1)BCryptPasswordEncoder的实例,直接调用 encode方法,此时version,strlength,random都采用默认值。
2)也可以使用BCrypt来实现,实际上上边BCypt的操作就是BCryptPasswordEncoder#encode内部的方法实现。
3)另外,也可以直接在代码中引入applicaitonContext-security.xml中的md5 bean到代码中 @Resources("bCryptPasswordEncoder") private PasswordEncoder bCryptPasswordEncoder;。
使用md5 bean
applicationContext-shiro.xml中配置
<bean id="md5" class="org.springframework.security.crypto.password.MessageDigestPasswordEncoder"> <constructor-arg name="algorithm" value="MD5"/> <property name="iterations" value="10"/> </bean> <security:authentication-manager> <security:authentication-provider> <security:user-service> <security:user name="user" password="{sBNW6rB991DqeGbH6ikVJcTe6XwPoHtPW/iyWkwbrF4=}38dee1075a2eaa458bc3fb7e7a945ef8" authorities="ROLE_USER"/> <security:user name="admin" password="{sBNW6rB991DqeGbH6ikVJcTe6XwPoHtPW/iyWkwbrF4=}38dee1075a2eaa458bc3fb7e7a945ef8" authorities="ROLE_USER, ROLE_ADMIN"/> </security:user-service> <security:password-encoder ref="md5"/> </security:authentication-provider> </security:authentication-manager>
说明:
1)需要配置md5 bean,在配置bean时,必须指定MessageDigestPasswordEncoder的构造函数参数:algorithm:指定算法类型,这里是MD5;
2)另外,md5#iterations参数:迭代次数如果不指定,默认为1,这里指定为10;
2)需要在<authentication-provider>标签下的<password-encoder ref=''/>指定该bean。
密码加密用法:
MessageDigestPasswordEncoder md5 = new MessageDigestPasswordEncoder("MD5"); md5.setIterations(10); md5Password = "{MD5}" + md5.encode("password"); System.out.println("MD5密码:" + md5Password); System.out.println("MD5密码对比:" + passwordEncoder.matches("password", md5Password));
在对密码加密时,可以采用上边方法:
1)MessageDigestPasswordEncoder的实例,可以设置其迭代次数。
2)另外,也可以直接在代码中引入applicaitonContext-security.xml中的md5 bean到代码中 @Resources("md5") private PasswordEncoder md5;。
缺省 password-encoder,使用 DelegatingPasswordEncoder ,自动适配 PasswordEncoder
applicationContext-shiro.xml中配置:
<security:authentication-manager> <security:authentication-provider> <security:user-service> <!-- noop NoOpPasswordEncoder.getInstance()--> <security:user name="user" password="{noop}userpwd" authorities="ROLE_USER"/> <security:user name="admin" password="{noop}adminpwd" authorities="ROLE_USER, ROLE_ADMIN"/> <!-- bcrypt new BCryptPasswordEncoder() --> <security:user name="user1" password="{bcrypt}$2a$10$LCe6jsoHUrEvWI1KURrqbu/xfuPU5aZj2RkPTVS0d7MUJiT55Lt/y" authorities="ROLE_USER"/> <security:user name="admin1" password="{bcrypt}$2a$10$BR3Np37NbmtWHqpSZE6AMeCMG4Rm.UOUEZ3dYrW3oUXHNuSBXjDwi" authorities="ROLE_USER, ROLE_ADMIN"/> <!-- MD5 new MessageDigestPasswordEncoder("MD5") --> <security:user name="user2" password="{MD5}{sBNW6rB991DqeGbH6ikVJcTe6XwPoHtPW/iyWkwbrF4=}38dee1075a2eaa458bc3fb7e7a945ef8" authorities="ROLE_USER"/> <security:user name="admin2" password="{MD5}{sBNW6rB991DqeGbH6ikVJcTe6XwPoHtPW/iyWkwbrF4=}38dee1075a2eaa458bc3fb7e7a945ef8" authorities="ROLE_USER, ROLE_ADMIN"/> </security:user-service> <security:password-encoder ref="md5"/> </security:authentication-provider> </security:authentication-manager>
1)如果在<security:authentication-provider>下指定了<security:password-encoder ref="xxx"/>就不需要在<security:user name="xxx" password="yyy"authorities="zzz"/>中的 password 前边加上加密类型({noop}、{bcrypt}、{MD5}等),否则会导致密码验证失败;
2)如果在<security:authentication-provider>下未指定<security:password-encoder ref="xxx"/>就必须要在<security:user name="xxx" password="yyy" authorities="zzz"/>中的 password 前边加上加密类型({noop}、{bcrypt}、{MD5}等),否则会导致密码验证失败。因为此时验证密码是否成功,会调用org.springframework.security.crypto.password.DelegatingPasswordEncoder.java中的encoder方法、matches方法,而DelegatingPasswordEncoder中查找密码加密对应的PasswordEncoder时,会根据密码前缀的加密类型查找:如果查找失败,会导致查找不到delegate,也就是delegate为null。