前言
Spring Security系列二 用户登录认证数据库实现中,我们已经把对用户的认证改成了数据库实现,功能上虽然完成了,但是用户的密码却都是以明文保存的,这在实际项目中安全系数上会有所欠缺。在本章中我们将实现如何对用户的密码进行加密。
Spring Security中的密码加密
在Spring Security
中,对密码的加密都是由PasswordEncoder
来完成的。
那什么时候会调用这个PasswordEncoder
呢?这就要回到前面实现数据库登录认证时的DaoAuthenticationProvider
了。在DaoAuthenticationProvider
中,除了UserDetailsService
之外还有其它的几个属性,其中一个就是PasswordEncoder
,UserDetailsService
前面我们已经实现了,现在要实现PasswordEncoder
,密码加密功能主要就是靠它来完成。
Spring Security中的PasswordEncoder
其实在Spring Security
中,已经对PasswordEncoder
有了很多实现,包括md5
加密、SHA-256
加密等等,一般情况下我们只要直接拿来用就可以了。
查看类DaoAuthenticationProvider
的setPasswordEncoder
方法:
public void setPasswordEncoder(Object passwordEncoder) {
...
}
会发现参数类型居然是Object
类型,这是因为在内置的PasswordEncoder
中,又分了两条路线,应该是随着版本的更新优化而衍生的,但为了兼容老版本所以两个都保留了下来,这里就都分别介绍一下。
老的PasswordEncoder
具体是指接口:org.springframework.security.authentication.encoding.PasswordEncoder
。
之所以说它老是因为在该接口上已经标了@Deprecated
注解不推荐使用了,但相应的实现类却没有标注,所以目前使用上依然是相当广泛的,很多人可能并不知道已经@Deprecated
了。
它的类图结构如下:
可以看到有很多常用的PasswordEncoder
已经有实现了,这里拿最常用的Md5PasswordEncoder
来做示例。
想要使用密码加密就必须指定使用哪个PasswordEncoder
,但是在AuthenticationManagerBuilder
中并没有可以快速指定PasswordEncoder
的地方,所以这里必须自己声明AuthenticationProvider
,然后设置UserDetailsService
和PasswordEncoder
,具体代码如下:
@Bean
public UserDetailsService userDetailsService() {
return new CustomUserDetailsService();
}
@Bean
public PasswordEncoder passwordEncoder(){
return new Md5PasswordEncoder();
}
@Bean
public AuthenticationProvider authenticationProvider(){
DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
authenticationProvider.setUserDetailsService(userDetailsService());
authenticationProvider.setPasswordEncoder(passwordEncoder());
return authenticationProvider;
}
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//auth.userDetailsService(userDetailsService());
auth.authenticationProvider(authenticationProvider());
}
需要注意在configure
中设置了AuthenticationProvider
就要把原先的auth.userDetailsService(userDetailsService())
去掉,不然就会有两个userDetailsService
的调用和认证,结果必然是一次正确一次不正确,返回你预期之外的结果。
新的PasswordEncoder
具体是指接口:org.springframework.security.crypto.password.PasswordEncoder
,这是spring当前推荐使用的接口。
它的类图如下:
实现类只有三个,简单明了,但加密安全性却提高了。
NoOpPasswordEncoder
不多说了,啥也不做按原文本处理,相当于不加密。
StandardPasswordEncoder
1024次迭代的SHA-256
散列哈希加密实现,并使用一个随机8字节的salt。
BCryptPasswordEncoder
使用BCrypt的强散列哈希加密实现,并可以由客户端指定加密的强度strength
,强度越高安全性自然就越高,默认为10.
在Spring
的注释中,明确写明了如果是开发一个新的项目,BCryptPasswordEncoder
是较好的选择。
* If you are developing a new system,
* {@link org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder} is a better
* choice both in terms of security and interoperability with other languages.
代码示例:
@Bean
public org.springframework.security.crypto.password.PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
authenticationProvider.setUserDetailsService(userDetailsService());
authenticationProvider.setPasswordEncoder(passwordEncoder());
return authenticationProvider;
}
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//auth.userDetailsService(userDetailsService());
auth.authenticationProvider(authenticationProvider());
}
给密码加点盐 salt
不得不说salt
这名字取的很贴切。
在很多时候我们可能需要给密码加点指定的前缀或后缀,以防止像123456
这类简单的密码被反向破解,这时候就会用到SaltSource
了。
其实SaltSource
随着PasswordEncoder
的更换目前已是不推荐使用了,但是有必要了解一下它,以及它背后的目的是什么,以实现更好的密码安全性。
SaltSource
类图如下:
SaltSource
的目的就是混淆一下密码然后再进行加密,防止加密后的字符串被反向破解。像ReflectionSaltSource
可以指定对象的某个属性值添加到密码中以增加安全性。
这里为简单起见,我们自己实现一个SaltSource
,在密码中加固定的字母abc
:
/**
* Created by liyd on 16/11/26.
*/
public class CustomSaltSource implements SaltSource {
@Override
public Object getSalt(UserDetails userDetails) {
return "abc";
}
}
然后指定使用CustomSaltSource
:
@Bean
public SaltSource saltSource() {
return new CustomSaltSource();
}
@Bean
public AuthenticationProvider authenticationProvider(){
DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
authenticationProvider.setUserDetailsService(userDetailsService());
authenticationProvider.setPasswordEncoder(passwordEncoder());
authenticationProvider.setSaltSource(saltSource());
return authenticationProvider;
}
启动项目,打上断点调试,发现在我们的密码123456
后面加上了abc
:
自然加密后的字符串也相应变了,加强了密码的安全性。
需要注意SaltSource
只针对老的PasswordEncoder
而言,新的PasswordEncoder
已经不需要使用SaltSource
来加强密码的安全性了,因为它的强度可以由用户指定,强度不同加密后的字符串自然也不同,安全性已经足够了,就算你想加也会抛出下面的异常:
java.lang.IllegalArgumentException: Salt value must be null when used with crypto module PasswordEncoder
密码的保存
以上说了密码的加密校验,有个前提当然是你在保存数据的时候密码加密方式得和这个保持一致,这个也不用自己实现,既然已经有了直接把PasswordEncoder
拿来用就行:
@Bean
public AuthenticationProvider authenticationProvider() {
//这里只做如何使用passwordEncoder与校验保持一致示例 密码输出
String password = passwordEncoder().encode("123456");
System.out.println(password);
...
}