前言

Spring Security系列二 用户登录认证数据库实现中,我们已经把对用户的认证改成了数据库实现,功能上虽然完成了,但是用户的密码却都是以明文保存的,这在实际项目中安全系数上会有所欠缺。在本章中我们将实现如何对用户的密码进行加密。

Spring Security中的密码加密

Spring Security中,对密码的加密都是由PasswordEncoder来完成的。

那什么时候会调用这个PasswordEncoder呢?这就要回到前面实现数据库登录认证时的DaoAuthenticationProvider了。在DaoAuthenticationProvider中,除了UserDetailsService之外还有其它的几个属性,其中一个就是PasswordEncoderUserDetailsService前面我们已经实现了,现在要实现PasswordEncoder,密码加密功能主要就是靠它来完成。

Spring Security中的PasswordEncoder

其实在Spring Security中,已经对PasswordEncoder有了很多实现,包括md5加密、SHA-256加密等等,一般情况下我们只要直接拿来用就可以了。

查看类DaoAuthenticationProvidersetPasswordEncoder方法:

public void setPasswordEncoder(Object passwordEncoder) {
    ...
}

会发现参数类型居然是Object类型,这是因为在内置的PasswordEncoder中,又分了两条路线,应该是随着版本的更新优化而衍生的,但为了兼容老版本所以两个都保留了下来,这里就都分别介绍一下。

老的PasswordEncoder

具体是指接口:org.springframework.security.authentication.encoding.PasswordEncoder

之所以说它老是因为在该接口上已经标了@Deprecated注解不推荐使用了,但相应的实现类却没有标注,所以目前使用上依然是相当广泛的,很多人可能并不知道已经@Deprecated了。

它的类图结构如下:

PasswordEncoder类结构图

可以看到有很多常用的PasswordEncoder已经有实现了,这里拿最常用的Md5PasswordEncoder来做示例。

想要使用密码加密就必须指定使用哪个PasswordEncoder,但是在AuthenticationManagerBuilder中并没有可以快速指定PasswordEncoder的地方,所以这里必须自己声明AuthenticationProvider,然后设置UserDetailsServicePasswordEncoder,具体代码如下:

@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当前推荐使用的接口。

它的类图如下:

新的PasswordEncoder类图

实现类只有三个,简单明了,但加密安全性却提高了。

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类图

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);
    ...
}

附件列表

你可能感兴趣的内容
Java权限控制算法 收藏,5738 浏览
[译]Gerrit 权限控制 收藏,4294 浏览
12条评论
dbssss 1年前

整的这么复杂

aichenglong 1年前

看看

dbssss 1年前

整的这么复杂

selfly

交流QQ群:32261424
Owner