记-JWT 认证接口性能调优
一、前言
1.服务器
4核16G
2.工具
工具 | 用途 |
---|---|
jmeter | 压力测试工具 |
arthas | 阿里开源的Java诊断工具 |
2.描述
使用jmeter压测jwt认证接口时发现,服务器CPU占用特别高,TPS总是上不去仅有
50 tps
二、问题排查
使用arthas 诊断工具连续打印CPU使用率高的线程堆栈,发现该方法占用CPU资源高
org.springframework.security.crypto.bcrypt.BCrypt#checkpw(java.lang.String, java.lang.String)
之前对BCryptPasswordEncoder
接触不多,于是顺便了解了下 Spring security BCryptPasswordEncoder密码验证原理。
BCryptPasswordEncoder
密文实际由BCryptVersion(版本号)
+strength(长度)
+ ‘SecureRandom(随机值即real_salt)’ +hashed(散列结果)
;也就是说BCryptPasswordEncoder
加密结果实际是加盐的hash结果,而盐不同,加密结果也就不相同,这也是为什么多次执行org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder#encode
加密操作返回结果不一样,这和MD5
不一样的地方。比对密码时,将数据库的hash结果作为salt(伪盐)
,利用org.springframework.security.crypto.bcrypt.BCrypt#checkpw(java.lang.String, java.lang.String)
提取真正的real_salt(z真盐)
,将传入的原始密码加密,real_salt
一样的情况下,重新hash的结果也是一样的,从而达到比对校验的目的。
示例数据:$2a 10 10 10SMx6IiquDTyLMrdr3n8o1.MBn/8.Y2u5hH.qVSGMafX3wsJR8VJ9q
1.加密
org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder#encode
public String encode(CharSequence rawPassword) {
if (rawPassword == null) {
throw new IllegalArgumentException("rawPassword cannot be null");
}
String salt;
if (random != null) {
salt = BCrypt.gensalt(version.getVersion(), strength, random);
} else {
salt = BCrypt.gensalt(version.getVersion(), strength);
}
return BCrypt.hashpw(rawPassword.toString(), salt);
}
2.匹配
org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder#matches
public boolean matches(CharSequence rawPassword, String encodedPassword) {
if (rawPassword == null) {
throw new IllegalArgumentException("rawPassword cannot be null");
}
if (encodedPassword == null || encodedPassword.length() == 0) {
logger.warn("Empty encoded password");
return false;
}
if (!BCRYPT_PATTERN.matcher(encodedPassword).matches()) {
logger.warn("Encoded password does not look like BCrypt");
return false;
}
return BCrypt.checkpw(rawPassword.toString(), encodedPassword);
}
3.示例程序
String password = "123456";
// passwordEncoder实例
final BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
// 加密
final String hashed = passwordEncoder.encode(password);
// 匹配结果
final boolean matches = passwordEncoder.matches(password, hashed);
匹配时,实际执行hash操作
BCrypt.checkpw(rawPassword.toString(), encodedPassword);
4.总结
查询资料发现,前面讲的
strength(长度)
对hash性能影响极大(指数级别),可用值431,默认值10。测试发现确实如此。`strength(长度)`为4时,单次操作24毫秒;strength(长度)
为20时,单次操作几分钟甚至更长时间。
三、解决办法
指定
strength(长度)
@Bean
public PasswordEncoder passwordEncoder() {
//一种基于随机生成salt的根据强大的哈希加密算法
return new BCryptPasswordEncoder(4);
}
四、效果
同一机器下,TPS从
50
提升值2000
左右,提升40倍。
arthas诊断神器-初探
jmeter入门示例
Spring security BCryptPasswordEncoder密码验证原理详解