Spring Security加密和匹配及原理解析

 更新时间:2023年10月25日 09:28:15   作者:oh LAN  
我们开发时进行密码加密,可用的加密手段有很多,比如对称加密、非对称加密、信息摘要等,本篇文章给大家介绍Spring Security加密和匹配及原理解析,感兴趣的朋友一起看看吧

一. 密码加密简介

1. 散列加密概述

我们开发时进行密码加密,可用的加密手段有很多,比如对称加密、非对称加密、信息摘要等。在一般的项目里,常用的就是信息摘要算法,也可以被称为散列加密函数,或者称为散列算法、哈希函数。这是一种可以从任何数据中创建数字“指纹”的方法,常用的散列函数有 MD5 消息摘要算法、安全散列算法(Secure Hash Algorithm)等。

2. 散列加密原理

散列函数通过把消息或数据压缩成摘要信息,使得数据量变小,将数据的格式固定下来,然后将数据打乱混合,再重新创建成一个散列值,从而达到加密的目的。散列值通常用一个短的随机字母和数字组成的字符串来代表,一个好的散列函数在输入域中很少出现散列冲突。在散列表和数据处理时,如果我们不抑制冲突来区别数据,会使得数据库中的记录很难找到。

但是仅仅使用散列函数还不够,如果我们只是单纯的使用散列函数而不做特殊处理,其实是有风险的!比如在两个用户密码明文相同时,生成的密文也会相同,这样就增加了密码泄漏的风险。

所以为了增加密码的安全性,一般在密码加密过程中还需要“加盐”,而所谓的“盐”可以是一个随机数,也可以是用户名。”加盐“之后,即使密码的明文相同,用户生成的密码密文也不相同,这就可以极大的提高密码的安全性。

传统的加盐方式需要在数据库中利用专门的字段来记录盐值,这个字段可以是用户名字段(因为用户名唯一),也可以是一个专门记录盐值的字段,但这样的配置比较繁琐。

二、SpringSecurity 中的密码源码分析

当我们项目只引入springsecurity依赖之后,接下来什么事情都不用做,我们直接来启动项目。

在项目启动过程中,我们会看到如下一行日志:

Using generated security password: 10abfb2j-36e1-446a-jh9b-f70024fc89ab

这就是 Spring Security 为默认用户 user 生成的临时密码,是一个 UUID 字符串。这个密码和用户相关的自动化配置类在 UserDetailsServiceAutoConfiguration 里边,在该类的 getOrDeducePassword 方法中,我们看到如下一行日志: 

if (user.isPasswordGenerated()) {
	logger.info(String.format("%n%nUsing generated security password: %s%n", user.getPassword()));
}

毫无疑问,我们在控制台看到的日志就是从这里打印出来的。打印的条件是 isPasswordGenerated 方法返回 true,即密码是默认生成的。

进而我们发现,user.getPassword 出现在 SecurityProperties 中,在 SecurityProperties 中我们看到如下定义:

/**
 * Default user name.
 */
private String name = "user";
/**
 * Password for the default user name.
 */
private String password = UUID.randomUUID().toString();
private boolean passwordGenerated = true;

 可以看到,默认的用户名就是 user,默认的密码则是 UUID,而默认情况下,passwordGenerated 也为 true。

SecurityProperties默认的用户就定义在它里边,是一个静态内部类,我们如果要定义自己的用户名密码,必然是要去覆盖默认配置,我们先来看下 SecurityProperties 的定义:

@ConfigurationProperties(prefix = "spring.security")
publicclass SecurityProperties {}

这就很清晰了,我们只需要以 spring.security.user 为前缀,去定义用户名密码即可:

spring.security.user.name=admin
spring.security.user.password=123456

这就是我们新定义的用户名密码。

在 properties 中定义的用户名密码最终是通过 set 方法注入到属性中去的,这里我们顺便来看下 SecurityProperties.User#setPassword 方法:

public void setPassword(String password) {
	if (!StringUtils.hasLength(password)) {
		return;
	}
	this.passwordGenerated = false;
	this.password = password;
}

从这里我们可以看到,application.properties 中定义的密码在注入进来之后,还顺便设置了 passwordGenerated 属性为 false,这个属性设置为 false 之后,控制台就不会打印默认的密码了。

此时重启项目,就可以使用自己定义的用户名/密码登录了

除了上面的配置文件这种方式之外,我们也可以在配置类中配置用户名/密码。

@Configuration
publicclass SecurityConfig extends WebSecurityConfigurerAdapter {
    @Bean
    PasswordEncoder passwordEncoder() {
        return NoOpPasswordEncoder.getInstance();
    }
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .withUser("admin")
                .password("123456").roles("admin");
    }
}

在配置类中配置,我们就要指定 PasswordEncoder 了,这是一个非常关键的东西

三、PasswordEncoder

1、PasswordEncoder

security中用于加密的接口就是PasswordEncoder,接口用于执行密码的单向转换,以便安全地存储密码,源码如下

public interface PasswordEncoder {
//该方法提供了明文密码的加密处理,加密后密文的格式主要取决于PasswordEncoder接口实现类实例。
    String encode(CharSequence rawPassword);
//匹配存储的密码以及登录时传递的密码(登录密码是经过加密处理后的字符串)是否匹配,如果匹配该方法则会返回true,第一个参数表示需要被解析的密码 第二个参数表示存储的密码
    boolean matches(CharSequence rawPassword, String encodedPassword);
 
    default boolean upgradeEncoding(String encodedPassword) {
        return false;
    }
}

PasswordEncoder 中的 encode 方法是我们在用户注册的时候手动调用,而matches 方法,则是由系统调用,默认是在 DaoAuthenticationProvider#additionalAuthenticationChecks 方法中调用的。

protected void additionalAuthenticationChecks(UserDetails userDetails,
  UsernamePasswordAuthenticationToken authentication)
  throws AuthenticationException {
 if (authentication.getCredentials() == null) {
  logger.debug("Authentication failed: no credentials provided");
  throw new BadCredentialsException(messages.getMessage(
    "AbstractUserDetailsAuthenticationProvider.badCredentials",
    "Bad credentials"));
 }
 String presentedPassword = authentication.getCredentials().toString();
 if (!passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
  logger.debug("Authentication failed: password does not match stored value");
  throw new BadCredentialsException(messages.getMessage(
    "AbstractUserDetailsAuthenticationProvider.badCredentials",
    "Bad credentials"));
 }
}

 可以看到,密码比对就是通过 passwordEncoder.matches 方法来进行的。

Spring Security 提供了多种密码加密方案,官方推荐使用 BCryptPasswordEncoder,BCryptPasswordEncoder 使用 BCrypt 强哈希函数,开发者在使用时可以选择提供 strength 和 SecureRandom 实例。strength 越大,密钥的迭代次数越多,密钥迭代次数为 2^strength。strength 取值在 4~31 之间,默认为 10。

不同于 Shiro 中需要自己处理密码加盐,在 Spring Security 中,BCryptPasswordEncoder 就自带了盐,处理起来非常方便。而 BCryptPasswordEncoder 就是 PasswordEncoder 接口的实现类。其他实现类列表如下

举例使用

三、hutool 工具 BCrypt 进行加密和匹配

( cn.hutool.crypto.digest.BCrypt )

import cn.hutool.crypto.digest.BCrypt;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
 
public class UmsMemberController {
    public static void main(String[] args) {
        String pas="123456";
        String pas1="$2a$10$mMslOUUCblGnotpq5G3j2er8OuqsIU08YF.x50//YOB6vLrGNd7Wq";
        BCryptPasswordEncoder n=new BCryptPasswordEncoder();
        System.out.println("加密前密码:"+pas);
        System.out.println("加密后密码:"+pas1);
        System.out.println("重新进行加密后密码:"+n.encode(pas));
        if (n.matches(pas,pas1)){
            System.out.println("True - 匹配:"+"11111111111111111111111");
        }else {
            System.out.println("False - 未匹配:"+"2222222222222222222222");
        }
        System.out.println("======================= 两种方法类似都可进行加密和匹配 =======================");
        if (BCrypt.checkpw(pas,pas1)){
            System.out.println("True - 匹配:"+"11111111111111111111111");
        }else {
            System.out.println("False - 未匹配:"+"2222222222222222222222");
        }
    }
}

到此这篇关于Spring Security加密和匹配的文章就介绍到这了,更多相关Spring Security加密和匹配内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • SpringMVC实现表单验证功能详解

    SpringMVC实现表单验证功能详解

    这篇文章主要为大家详细介绍了SpringMVC 表单验证的相关资料,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-10-10
  • 部署springboot项目读取外部配置文件实现过程

    部署springboot项目读取外部配置文件实现过程

    本文介绍了Spring Boot项目如何读取外部配置文件,以实现应用与配置分离,主要通过将配置文件放在jar包的同级目录中,或通过启动命令指定配置文件路径等方式实现,这种方法可以避免每次部署时修改配置文件的麻烦
    2026-04-04
  • Spring Boot、Redis、RabbitMQ 在项目中的核心作用详解(代码示例)

    Spring Boot、Redis、RabbitMQ 在项目中的核心作用详解(代码示例)

    在现代企业级应用开发中,Spring Boot、Redis 和 RabbitMQ 已经成为不可或缺的技术组件,本文将深入剖析这三者在实际项目中的作用,并通过代码示例和流程图展示它们的实际应用,感兴趣的朋友一起看看吧
    2025-11-11
  • Java之类加载机制案例讲解

    Java之类加载机制案例讲解

    这篇文章主要介绍了Java之类加载机制案例讲解,本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下
    2021-07-07
  • 在SpringBoot中如何利用Redis实现互斥锁

    在SpringBoot中如何利用Redis实现互斥锁

    当我们利用Redis存储热点数据时,突然就过期失效或者被删除了,导致大量请求同时访问数据库,增加了数据库的负载,为减轻数据库的负载我们利用互斥锁,本文重点介绍在SpringBoot中如何利用Redis实现互斥锁,感兴趣的朋友一起看看吧
    2023-09-09
  • java web学习_浅谈request对象中get和post的差异

    java web学习_浅谈request对象中get和post的差异

    下面小编就为大家带来一篇java web学习_浅谈request对象中get和post的差异。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-05-05
  • Maven中jar包冲突原理与解决办法

    Maven中jar包冲突原理与解决办法

    这篇文章主要介绍了Maven中jar包冲突原理与解决办法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-09-09
  • Spring中的策略模式简单实现与使用分析

    Spring中的策略模式简单实现与使用分析

    这篇文章主要介绍了Spring中的策略模式简单实现与使用分析,去初始化时除了 initMultipartResolver(上传文件)没有获取 Properties defaultStrategies;默认策略,其他的八大件都会使用到策略模式,需要的朋友可以参考下
    2024-01-01
  • Java实现Redis的集合(set)命令操作

    Java实现Redis的集合(set)命令操作

    这篇文章主要介绍了Java实现Redis的集合(set)命令操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-07-07
  • Linux下启动jar包的几种常见方式

    Linux下启动jar包的几种常见方式

    最近在linux上发布了一个SpringBoot项目,研究了一下jar包在Linux上的集中启动方式,这篇文章主要给大家介绍了Linux下启动jar包的几种常见方式,需要的朋友可以参考下
    2023-07-07

最新评论