Spring Security实战(五)—— 密码加密

一、密码加密的演进

MD5 (Message-Digest Algorithm 5) 和 SHA (Secure Hash Algorithm) 是两种常见的消息摘要算法,它们都被用于加密和数据完整性验证等领域。

MD5 是一种 128 位的哈希函数,常用于数据完整性校验和数字签名等方面。它将任意长度的信息映射为一个 128 位的摘要值,输出的值通常表示为一个 32 位的十六进制数。MD5 的安全性已经被破解,因此现在不再被广泛使用。

SHA 系列算法有多个版本,比较常见的有 SHA-1、SHA-256、SHA-384 和 SHA-512。它们都是将输入数据映射为一个固定长度的输出摘要值。SHA-1 生成的摘要长度为 160 位,而 SHA-256、SHA-384 和 SHA-512 分别生成 256 位、384 位和 512 位的摘要值。SHA-256 是目前应用最广泛的 SHA 算法之一,它的输出长度和安全性都比 MD5 更高。

Bcrypt 是一种密码哈希函数,可以用于对密码进行加密和验证。它是一种慢速哈希函数,其设计目的是为了防止暴力破解攻击和彩虹表攻击。Bcrypt 还可以通过增加 salt(随机值)和工作因子(算法运行次数)来进一步增强安全性。

Bcrypt 算法的实现中,会将明文密码和 salt 进行混合,并经过多轮哈希计算,最终得到一个长度为 60 个字符的密文。这个密文包括算法标识符、salt、工作因子和哈希值等信息。在验证密码时,Bcrypt 会从密文中提取出 salt 和工作因子,然后使用相同的哈希算法和参数来计算输入密码的哈希值,最后比较计算出的哈希值和密文中的哈希值是否一致。

由于 Bcrypt 算法的计算量比较大,因此它可以有效地防止暴力破解攻击和彩虹表攻击。同时,使用不同的 salt 和工作因子可以让相同的密码在不同的计算中生成不同的哈希值,进一步增加了破解难度。

因为其安全性较高,Bcrypt 已成为许多 Web 应用程序中默认的密码哈希函数,如 Ruby on Rails、Django 等。

Spring Security的密码加密机制

 Spring Security内置了密码加密机制,只需使用一个PasswordEncoder接口即可。

public interface PasswordEncoder {
    String encode(CharSequence rawPassword);

    boolean matches(CharSequence rawPassword, String encodedPassword);

    default boolean upgradeEncoding(String encodedPassword) {
        return false;
    }
}

这是一个密码编码器的 Java 接口定义,它规定了密码编码器需要实现的方法:

  • encode(CharSequence rawPassword) 方法用于对原始密码进行编码,返回编码后的密码字符串。
  • matches(CharSequence rawPassword, String encodedPassword) 方法用于检查原始密码和已编码密码是否匹配,返回匹配结果。这个方法在密码验证时被使用。
  • upgradeEncoding(String encodedPassword) 方法用于检查已编码密码是否需要升级加密,如果需要升级则返回 true,否则返回 false

该接口的具体实现可以使用不同的密码哈希算法,如 Bcrypt、SHA-256 等。使用密码编码器的主要目的是提高密码的安全性,将原始密码转化为不可逆的密文。在验证密码时,只需要比较原始密码经过编码后得到的密文和存储在数据库中的已编码密码是否匹配即可。

一些常用的实现类:

 (1)先对一个字符串进行加密

对密码 admin123 进行BCrypt加密,加密结果是:

$2a$10$cDqQrQ7OErpa78o8Wa8otekXnnwpGAOBHw5PXHaEjiNOB5ZJAsf9O

然后将数据库用户 admin 的密码修改为该密码:

(2)配置spring security

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService)
                .passwordEncoder(passwordEncoder());
    }

具体逻辑:

(1)提交用户名和密码之后,会将用户名和密码传给UsernamePasswordAuthenticationFilter

        UsernamePasswordAuthenticationFilter是Spring Security中一个非常重要的过滤器,它负责处理基于表单的身份验证,即当用户提交包含用户名和密码的表单时,该过滤器会从该请求中提取用户名和密码并进行身份验证。 

(2)UsernamePasswordAuthenticationFilter调用authenticate() 方法进行认证

        执行该方法时,它会获取请求中的用户名和密码参数,然后调用 AuthenticationManager 对象的 authenticate() 方法来进行身份验证。如果身份验证成功,则创建一个 Authentication 对象并将其传递给 SecurityContextHolder 中。如果身份验证失败,则会抛出 AuthenticationException。

 (3)AuthenticationManager 会继续调用DaoAuthenticationProvider的authenticate() 进行验证

        DaoAuthenticationProvider是一个AuthenticationProvider的实现类,用于处理用户名和密码验证的过程。当AuthenticationManager调用authenticate()方法时,实际上是委托给了DaoAuthenticationProvider来处理认证请求。

(4)DaoAuthenticationProvider也会调用 loadUserByUsername() 方法查询用户

        在DaoAuthenticationProvider的authenticate()方法中,会先调用UserDetailsService的loadUserByUsername()方法获取用户信息,(这里根据实际情况可能去内存中查找,也可以去数据库查找)然后再将获取到的用户信息与用户输入的密码进行比较,最终确定用户是否通过认证。因此,在认证流程中,loadUserByUsername()方法是一个非常重要的环节。

(5)返回 UserDetail对象

        loadUserByUsername() 方法主要是根据给定的用户名查询用户信息,并返回一个 UserDetails 对象。该方法的具体实现可能会涉及到访问数据库或其他存储设备,以获取用户的详细信息。在 Spring Security 中,这个方法通常由 UserDetailsService 的实现类来完成。在实现类中,通常会根据用户名查询用户信息,并将其封装为一个 UserDetails 对象,以便后续的身份验证过程中使用。UserDetails 对象包含用户的身份信息、授权信息和其他详细信息,如密码和是否启用等。

(6)通过PasswordEncoder对象对比UserDetail的密码和提交的密码是否一致

        通过PasswordEncoder对象将用户输入的密码加密,然后与UserDetails中存储的加密后的密码进行比较,来验证用户的身份。如果两者一致,则认为用户身份验证通过。PasswordEncoder主要用于将密码进行加密,以提高安全性。

(7)如果正确就把UserDetails中的权限信息设置到Authentication对象中

(8)返回Authentication对象

(9)将认证成功的 Authentication 对象存储在 SecurityContextHolder 中

(3)测试

 输入admin admin123 可以登陆成功

猜你喜欢

转载自blog.csdn.net/weixin_49561506/article/details/130191103