SpringBoot中密码加密的两种⽅法
先说⼀句:密码是⽆法解密的。⼤家也不要再问松哥微⼈事项⽬中的密码怎么解密了!
密码⽆法解密,还是为了确保系统安全。今天松哥就来和⼤家聊⼀聊,密码要如何处理,才能在最⼤程度上确保我们的系统安全。
1.为什么要加密
2011 年 12 ⽉ 21 ⽇,有⼈在⽹络上公开了⼀个包含 600 万个 CSDN ⽤户资料的数据库,数据全部为明⽂储存,包含⽤户名、密码以及注册邮箱。事件发⽣后 CSDN 在微博、官⽅⽹站等渠道发出了声明,解释说此数据库系 2009 年备份所⽤,因不明原因泄露,已经向警⽅报案,后⼜在官⽹发出了公开道歉信。在接下来的⼗多天⾥,⾦⼭、⽹易、京东、当当、新浪等多家公司被卷⼊到这次事件中。整个事件中最触⽬惊⼼的莫过于 CSDN 把⽤户密码明⽂存储,由于很多⽤户是多个⽹站共⽤⼀个密码,因此⼀个⽹站密码泄露就会造成很⼤的安全隐患。由于有了这么多前车之鉴,我们现在做系统时,密码都要加密处理。
这次泄密,也留下了⼀些有趣的事情,特别是对于⼴⼤程序员设置密码这⼀项。⼈们从 CSDN 泄密的⽂件中,发现了⼀些好玩的密码,例如如下这些:
ppnn13%dkstFeb.1st 这段密码的中⽂解析是:娉娉袅袅⼗三余,⾖蔻梢头⼆⽉初。
csbt34.ydhl12s 这段密码的中⽂解析是:池上碧苔三四点,叶底黄鹂⼀两声
...
等等不⼀⽽⾜,你会发现很多程序员的⼈⽂素养还是⾮常⾼的,让⼈啧啧称奇。
2.加密⽅案
密码加密我们⼀般会⽤到散列函数,⼜称散列算法、哈希函数,这是⼀种从任何数据中创建数字“指纹”的⽅法。
散列函数把消息或数据压缩成摘要,使得数据量变⼩,将数据的格式固定下来,然后将数据打乱混合,重新创建⼀个散列值。散列值通常⽤⼀个短的随机字母和数字组成的字符串来代表。好的散列函数在输⼊域中很少出现散列冲突。在散列表和数据处理中,不抑制冲突来区别数据,会使得数据库记录更难到。
我们常⽤的散列函数有 MD5 消息摘要算法、安全散列算法(Secure Hash Algorithm)。
但是仅仅使⽤散列函数还不够,单纯的只使⽤散列函数,如果两个⽤户密码明⽂相同,⽣成的密⽂也会相同,这样就增加的密码泄漏的风险。
为了增加密码的安全性,⼀般在密码加密过程中还需要加盐,所谓的盐可以是⼀个随机数也可以是⽤户名,加盐之后,即使密码明⽂相同的⽤户⽣成的密码密⽂也不相同,这可以极⼤的提⾼密码的安全性。
传统的加盐⽅式需要在数据库中有专门的字段来记录盐值,这个字段可能是⽤户名字段(因为⽤户名唯⼀),也可能是⼀个专门记录盐值的字段,这样的配置⽐较繁琐。
Spring Security 提供了多种密码加密⽅案,官⽅推荐使⽤ BCryptPasswordEncoder,BCryptPasswordEncoder 使⽤ BCrypt 强哈希函数,开发者在使⽤时可以选择提供 strength 和 SecureRandom 实例。strength 越⼤,密钥的迭代次数越多,密钥迭代次数为 2^strength。strength 取值在 4~31 之间,默认为 10。
不同于 Shiro 中需要⾃⼰处理密码加盐,在 Spring Security 中,BCryptPasswordEncoder 就⾃带了盐,处理起来⾮常⽅便。
3.实践
3.1 codec 加密
所以,这⾥我先来给⼤家介绍下 commons-codec 的⽤法。
⾸先我们需要引⼊ commons-codec 的依赖:
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.11</version>
</dependency>
然后⾃定义⼀个 PasswordEncoder:
@Component
public class MyPasswordEncoder implements PasswordEncoder {
@Override
public String encode(CharSequence rawPassword) {
return DigestUtils.String().getBytes());
}
@Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {
return encodedPassword.equals(DigestUtils.String().getBytes()));
}
}
在 Spring Security 中,PasswordEncoder 专门⽤来处理密码的加密与⽐对⼯作,我们⾃定义 MyPasswordEncoder 并实现 PasswordEncoder 接⼝,还需要实现该接⼝中的两个⽅法:
1. encode ⽅法表⽰对密码进⾏加密,参数 rawPassword 就是你传⼊的明⽂密码,返回的则是加密之后的密⽂,这⾥的加密⽅案采⽤了 MD5。
2. matches ⽅法表⽰对密码进⾏⽐对,参数 rawPassword 相当于是⽤户登录时传⼊的密码,encodedPassword 则相当于是加密后的密码(从数据库中查询⽽来)。
最后记得将 MyPasswordEncoder 通过 @Component 注解标记为 Spring 容器中的⼀个组件。
这样⽤户在登录时,就会⾃动调⽤ matches ⽅法进⾏密码⽐对。
当然,使⽤了 MyPasswordEncoder 之后,在⽤户注册时,就需要将密码加密之后存⼊数据库中,⽅式如下:
public int reg(User user) {
...
//插⼊⽤户,插⼊之前先对密码进⾏加密
user.Password()));
result = (user);
...
}
3.2 BCryptPasswordEncoder 加密
但是⾃⼰定义 PasswordEncoder 还是有些⿇烦,特别是处理密码加盐问题的时候。
@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(10);
}
springboot推荐算法创建 BCryptPasswordEncoder 时传⼊的参数 10 就是 strength,即密钥的迭代次数(也可以不配置,默认为 10)。同时,配置的内存⽤户的密码也不再是 123 了,如下:
auth.inMemoryAuthentication()
.withUser("admin")
.password("$2a$10$RMuFXGQ5AtH4wOvkUqyvuecpqUSeoxZYqilXzbz50dceRsga.WYiq")
.roles("ADMIN", "USER")
.and()
.withUser("sang")
.password("$2a$10$eUHbAOMq4bpxTvOVz33LIehLe3fu6NwqC9tdOcxJXEhyZ4simqXTC")
.roles("USER");
这⾥的密码就是使⽤ BCryptPasswordEncoder 加密后的密码,虽然 admin 和 sang 加密后的密码不⼀样,但是明⽂都是 123。配置完成后,使⽤ admin/123 或者 sang/123 就可以实现登录。
本案例使⽤了配置在内存中的⽤户,⼀般情况下,⽤户信息是存储在数据库中的,因此需要在⽤户注册时对密码进⾏加密处理,如下:
@Service
public class RegService {
public int reg(String username, String password) {
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(10);
String encodePasswod = de(password);
return saveToDb(username, encodePasswod);
}
}
⽤户将密码从前端传来之后,通过调⽤ BCryptPasswordEncoder 实例中的 encode ⽅法对密码进⾏加密处理,加密完成后将密⽂存⼊数据库。
4.源码浅析
最后我们再来稍微看⼀下 PasswordEncoder。
PasswordEncoder 是⼀个接⼝,⾥边只有三个⽅法:
public interface PasswordEncoder {
String encode(CharSequence rawPassword);
boolean matches(CharSequence rawPassword, String encodedPassword);
default boolean upgradeEncoding(String encodedPassword) {
return false;
}
}
encode ⽅法⽤来对密码进⾏加密。
matches ⽅法⽤来对密码进⾏⽐对。
upgradeEncoding 表⽰是否需要对密码进⾏再次加密以使得密码更加安全,默认为 false。
Spring Security 为 PasswordEncoder 提供了很多实现:
但是⽼实说,⾃从有了 BCryptPasswordEncoder,我们很少关注其他实现类了。
PasswordEncoder 中的 encode ⽅法,是我们在⽤户注册的时候⼿动调⽤。
matches ⽅法,则是由系统调⽤,默认是在 DaoAuthenticationProvider#additionalAuthenticationChecks ⽅法中调⽤的。
protected void additionalAuthenticationChecks(UserDetails userDetails,
UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
if (Credentials() == null) {
logger.debug("Authentication failed: no credentials provided");
throw new Message(
"AbstractUserDetailsAuthenticationProvider.badCredentials",
"Bad credentials"));
}
String presentedPassword = Credentials().toString();
if (!passwordEncoder.matches(presentedPassword, Password())) {
logger.debug("Authentication failed: password does not match stored value");
throw new Message(
"AbstractUserDetailsAuthenticationProvider.badCredentials",
"Bad credentials"));
}
}
可以看到,密码⽐对就是通过 passwordEncoder.matches ⽅法来进⾏的。
关于 DaoAuthenticationProvider 的调⽤流程,⼤家可以参考。
好了,今天就和⼩伙伴们简单聊⼀聊 Spring Security 加密问题,⼩伙伴们要是有收获记得点个在看⿎励下松哥哦~
以上就是Spring Boot 中密码加密的两种⽅法的详细内容,更多关于Spring Boot 密码加密的资料请关注其它相关⽂章!
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论