Spring Security加密和匹配及原理解析
作者:oh LAN
一. 密码加密简介
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加密和匹配内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!