记一次使用BCryptPasswordEncoder,设置了不合理参数导致耗时严重的坑
导读
在项目开发中,越来越重视安全相关的功能。在使用Spring Boot进行项目开发的时候,使用Spring Security框架是一个不错的选择。
开发登录认证功能的时候,一般情况都不会将原始密码明文存储到数据库中,那么就需要对密码进行加密,Spring Security推荐使用的是BCryptPasswordEncoder
,说明它有可取之处。
问题
问题:在登录认证的时候,每次均需耗费5S以上的时间,这是用户无法忍受的事情。
排查过程:通过visualVM 的线程Dump的信息发现,在自定义的认证过滤器中的attemptAuthentication()
方法进入认证之后,在等待验证密码成功返回的时间最长。将目标放在密码比对的方法上,由于注入的是BCryptPasswordEncoder
,找到它调用的比对方法matches()
,传入原始密码和数据库中的密码,返回是否匹配的布尔值。
既然发现目标了,那就去扣一扣它的源码吧。
BCryptPasswordEncoder.class
/**
* Implementation of PasswordEncoder that uses the BCrypt strong hashing function. Clients
* can optionally supply a "version" ($2a, $2b, $2y) and a "strength" (a.k.a. log rounds
* in BCrypt) and a SecureRandom instance. The larger the strength parameter the more work
* will have to be done (exponentially) to hash the passwords. The default value is 10.
*
* @author Dave Syer
*/
private Pattern BCRYPT_PATTERN = Pattern.compile("\A\$2(a|y|b)?\$(\d\d)\$[./0-9A-Za-z]{53}");
private final int strength;
private final BCryptVersion version;
private final SecureRandom random;
通过注释,不难发现,生成的密码字符串有一定的规律,构造方法里面可选参数有三个,strength
,version
,random
参数strength
:默认值为10,可选值为4-31;
参数version
:默认值为$2a,可选值为$2a, $2b, $2y;
参数random
:SecureRandom的实例,默认值为空;
该类中生成密码的方法encode()
需要传入原始密码字符串,调用算法实现类BCrypt
的hashpw()
方法,增加一个salt
参数,该参数的值由BCrypt
的gensalt()
结合strength, version, random
生成。
进行密码比较,也就是调用matches()
方法的时候,首先需要将用户上传的密码(原始密码)进行加密,因此可以得出耗时的操作在于将原始密码加密。
当需要加密的密码相同时,可能影响性能的因素就只有strength, version, random
,使用控制变量法来进行测试,但是参数random
没有太大测试的意义。
1.改变参数strength
的值,保持其余两个值为默认值。
// strength设置为10,也就是默认值时
String password = new BCryptPasswordEncoder(10).encode("password");
System.out.println(password);
// strength设置为16的时候,不要问我为什么测试16,问就是我当时手抽设置的就是16
String password = new BCryptPasswordEncoder(16).encode("password");
System.out.println(password);
// strength设置为20的时候
String password = new BCryptPasswordEncoder(20).encode("password");
System.out.println(password);
由于strength
数值越大,耗时越严重,后面的值不测试了(因为测试了25,运行了19分钟都没有出来)。
2.改变参数version
的值,保持其余两个值为默认值。
参数version
设置为$2a,由于是默认值,故同测试1中的第二个用例。
// versionh设置为$2b
String password = new BCryptPasswordEncoder(BCryptPasswordEncoder.BCryptVersion.$2B, 16).encode("password");
System.out.println(password);
// version设置为$2y
String password = new BCryptPasswordEncoder(BCryptPasswordEncoder.BCryptVersion.$2Y, 16).encode("password");
System.out.println(password);
总结
通过以上测试可以看出,参数strength
的值改变对于生成加密密码的时间影响是最大的。
为甚麽记录这篇随笔?希望对遇到同样问题的你有所帮助。
PS:研究到这里就结束了,保住头发要紧。菜鸟开发第一次写博客,请各位看官轻喷~