文章目录

  • 1. PasswordEncoder 接口
  • 2. DelegatingPasswordEncoder
  • 3. 实战
  • 4. 加密方案自动升级

1. PasswordEncoder 接口

SpringSecurity中通过PasswordEncoder接口定义了密码加密和比对的相关操作:

public interface PasswordEncoder {// 对明文密码进行加密String encode(CharSequence var1);// 进行密码比对boolean matches(CharSequence var1, String var2);// 判断当前密码是否需要升级,默认返回false,表示不需要升级default boolean upgradeEncoding(String encodedPassword) {return false;}
}

针对密码的所有操作,PasswordEncoder都定义好了,不同的实现类将采用不同的密码加密方案对密码进行处理。

使用自适应单向函数处理密码问题的常见实现类:

BCryptPasswordEncoder

Argon2PasswordEncoder

SCryptPasswordEncoder

Pbkdf2PasswordEncoder

2. DelegatingPasswordEncoder

SpringSecurity中默认的密码加密方式为DelegatingPasswordEncoder,主要用于代理上面介绍的不同的密码加密方案。

先来看下PasswordEncoderFactories类,因为正是由它里面的静态方法createDelegatingPasswordEncoder()提供了默认的DelegatingPasswordEncoder实例:

public class PasswordEncoderFactories {public static PasswordEncoder createDelegatingPasswordEncoder() {String encodingId = "bcrypt";Map<String, PasswordEncoder> encoders = new HashMap();encoders.put(encodingId, new BCryptPasswordEncoder());encoders.put("ldap", new LdapShaPasswordEncoder());encoders.put("MD4", new Md4PasswordEncoder());encoders.put("MD5", new MessageDigestPasswordEncoder("MD5"));encoders.put("noop", NoOpPasswordEncoder.getInstance());encoders.put("pbkdf2", new Pbkdf2PasswordEncoder());encoders.put("scrypt", new SCryptPasswordEncoder());encoders.put("SHA-1", new MessageDigestPasswordEncoder("SHA-1"));encoders.put("SHA-256", new MessageDigestPasswordEncoder("SHA-256"));encoders.put("sha256", new StandardPasswordEncoder());encoders.put("argon2", new Argon2PasswordEncoder());return new DelegatingPasswordEncoder(encodingId, encoders);}private PasswordEncoderFactories() {}
}

createDelegatingPasswordEncoder() 方法中,首先定义了encoders变量,encoders中存储了每一种密码加密方案的id和所对应的加密类,例如MD4对应Md4PasswordEncoder,noop对应NoOpPasswordEncoder,其中encodingId的默认值为bcrypt,相当于默认使用的加密方案是BCryptPasswordEncoder。

再来看下DelegatingPasswordEncoder类的源码,由于源码较长,先来看下属性和构造方法:

public class DelegatingPasswordEncoder implements PasswordEncoder {}
// 定义前缀PREFIX和后缀SUFFIX用来包裹将来生成的加密方案的id
private static final String PREFIX = "{";
private static final String SUFFIX = "}";
// 默认的加密方案id
private final String idForEncode;
// 表示默认的加密方案(BCryptPasswordEncoder)
// 它的值是根据idForEncode从idToPasswordEncoder集合中提取出来的
private final PasswordEncoder passwordEncoderForEncode;
// 用来保存id和家吗方案之间的映射
private final Map<String, PasswordEncoder> idToPasswordEncoder;
// 默认的密码比对器,当根据密码加密方案的id无法找到对应的加密方案时,就会使用默认的密码比对器
// defaultPasswordEncoderForMatches的默认类型是UnmappedIdPasswordEncoder
// 在UnmappedIdPasswordEncoder的matches方法中不会做任何密码比对操作,直接抛出异常
private PasswordEncoder defaultPasswordEncoderForMatches = new DelegatingPasswordEncoder.UnmappedIdPasswordEncoder();public DelegatingPasswordEncoder(String idForEncode, Map<String, PasswordEncoder> idToPasswordEncoder) {if (idForEncode == null) {throw new IllegalArgumentException("idForEncode cannot be null");} else if (!idToPasswordEncoder.containsKey(idForEncode)) {throw new IllegalArgumentException("idForEncode " + idForEncode + "is not found in idToPasswordEncoder " + idToPasswordEncoder);} else {Iterator var3 = idToPasswordEncoder.keySet().iterator();while(var3.hasNext()) {String id = (String)var3.next();if (id != null) {if (id.contains("{")) {throw new IllegalArgumentException("id " + id + " cannot contain " + "{");}if (id.contains("}")) {throw new IllegalArgumentException("id " + id + " cannot contain " + "}");}}}this.idForEncode = idForEncode;// 根据idForEncode获取passwordEncoderForEncodethis.passwordEncoderForEncode = (PasswordEncoder)idToPasswordEncoder.get(idForEncode);this.idToPasswordEncoder = new HashMap(idToPasswordEncoder);}
}public void setDefaultPasswordEncoderForMatches(PasswordEncoder defaultPasswordEncoderForMatches) {if (defaultPasswordEncoderForMatches == null) {throw new IllegalArgumentException("defaultPasswordEncoderForMatches cannot be null");} else {this.defaultPasswordEncoderForMatches = defaultPasswordEncoderForMatches;}
}private class UnmappedIdPasswordEncoder implements PasswordEncoder {private UnmappedIdPasswordEncoder() {}public String encode(CharSequence rawPassword) {throw new UnsupportedOperationException("encode is not supported");}public boolean matches(CharSequence rawPassword, String prefixEncodedPassword) {String id = DelegatingPasswordEncoder.this.extractId(prefixEncodedPassword);throw new IllegalArgumentException("There is no PasswordEncoder mapped for the id \"" + id + "\"");}
}

encode()方法实现:

// 具体的加密工作仍然由加密类来完成
// 只不过在密码加密完成后,给加密后的字符串加上一个前缀{id},用来描述所采用的具体加密方案。
public String encode(CharSequence rawPassword) {return "{" + this.idForEncode + "}" + this.passwordEncoderForEncode.encode(rawPassword);
}

不同的前缀代表了后面的字符串采用了不同的加密方案。

matches()方法实现:

public boolean matches(CharSequence rawPassword, String prefixEncodedPassword) {if (rawPassword == null && prefixEncodedPassword == null) {return true;} else {// 从加密字符串中提取出具体的加密方案id,也就是{}中的字符String id = this.extractId(prefixEncodedPassword);// 到集合中获取具体的加密方案PasswordEncoder delegate = (PasswordEncoder)this.idToPasswordEncoder.get(id);// 如果获取到的为null,说明不存在对应的加密实例// 那么就采用默认的密码匹配器defaultPasswordEncoderForMatchesif (delegate == null) {return this.defaultPasswordEncoderForMatches.matches(rawPassword, prefixEncodedPassword);} else {// 如果获取到了对应的加密实例,则调用matches()方法完成密码校验String encodedPassword = this.extractEncodedPassword(prefixEncodedPassword);return delegate.matches(rawPassword, encodedPassword);}}
}// 从加密字符串中提取出具体的加密方案id,也就是{}中的字符,具体的提取方式就是字符串截取
private String extractId(String prefixEncodedPassword) {if (prefixEncodedPassword == null) {return null;} else {int start = prefixEncodedPassword.indexOf("{");if (start != 0) {return null;} else {int end = prefixEncodedPassword.indexOf("}", start);return end < 0 ? null : prefixEncodedPassword.substring(start + 1, end);}}
}private String extractEncodedPassword(String prefixEncodedPassword) {int start = prefixEncodedPassword.indexOf("}");return prefixEncodedPassword.substring(start + 1);
}

upgradeEncoding() 方法实现:

public boolean upgradeEncoding(String prefixEncodedPassword) {String id = this.extractId(prefixEncodedPassword);// 如果当前加密字符串所采用的加密方案不是默认的加密方案(BCryptPasswordEncoder)// 就会自动进行密码升级if (!this.idForEncode.equalsIgnoreCase(id)) {return true;} else {// 调用默认加密方案的upgradeEncoding()方法怕暖密码是否需要升级String encodedPassword = this.extractEncodedPassword(prefixEncodedPassword);return ((PasswordEncoder)this.idToPasswordEncoder.get(id)).upgradeEncoding(encodedPassword);}
}

3. 实战

① 创建一个SpringBoot工程,并引入SpringSecurity依赖,创建一个测试接口:

@RestController
public class UserResource {@RequestMapping("/hello")public String test(){return "hello";}
}

② 在单元测试中生成一段加密字符串:

@SpringBootTest
public class UuaApplicationTest {@Testpublic void test(){BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();// $2a$10$.758pfnk63n7fUNPLovJS.ydgs0gpSqH0nFrlxVTj/vLozzIk01NSSystem.out.println(encoder.encode("123"));}
}

③ 自定义SecurityConfig类:

@EnableWebSecurity(debug = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {// 表单登录@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().anyRequest().authenticated().and().formLogin().and().csrf().disable();}// 自定义用户认证@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.inMemoryAuthentication().withUser("zhangsan").password("$2a$10$.758pfnk63n7fUNPLovJS.ydgs0gpSqH0nFrlxVTj/vLozzIk01NS").roles("admin");}// 将BCryptPasswordEncoder示例注册到Spring容器中,这将代替默认的DelegatingPasswordEncoder@BeanPasswordEncoder passwordEncoder(){return new BCryptPasswordEncoder();}
}

配置完成后启动项目就可以使用zhangsan/123进行登录了。

由于默认使用的是DelegatingPasswordEncoder,所以页可以不配置PasswordEncode实例,只需要在密码前加上前缀{bcrypt}:

@EnableWebSecurity(debug = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().anyRequest().authenticated().and().formLogin().and().csrf().disable();}@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.inMemoryAuthentication().withUser("zhangsan").password("{bcrypt}$2a$10$.758pfnk63n7fUNPLovJS.ydgs0gpSqH0nFrlxVTj/vLozzIk01NS").roles("admin");}
}

4. 加密方案自动升级

使用DelegatingPasswordEncoder的另一个好处是会自动进行密码加密升级。

① 创建一个数据库test,向数据库中添加一个user表,并添加一条数据,在用户数据中,用户密码是{noop}123:

create table user(id int(11) not null auto_increment,username varchar(32) default null,password varchar(255) default null,primary key(id)
)engine=innodb default charset=utf8INSERT INTO `test`.`user` (`id`, `username`, `password`) VALUES (1, 'zhangsan', '{noop}123');

② 在项目中引入MyBatis和mysql依赖:

<dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.1.3</version>
</dependency>
<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId>
</dependency>

③ 在 application.properties 中添加数据库的连接信息:

spring:datasource:username: rootpassword: rooturl: jdbc:mysql://localhost:3306/test?characterEncoding=utf-8&useSSL=false&serverTimezone=Hongkong

④ 创建 User 实体类:

@Data
public class User implements UserDetails {private Integer id;private String username;private String password;@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {return null;}@Overridepublic String getPassword() {return password;}@Overridepublic String getUsername() {return username;}@Overridepublic boolean isAccountNonExpired() {return true;}@Overridepublic boolean isAccountNonLocked() {return true;}@Overridepublic boolean isCredentialsNonExpired() {return true;}@Overridepublic boolean isEnabled() {return true;}
}

⑤ 创建MyUserDetailsService类:

@Service
public class MyUserDetailsService implements UserDetailsService, UserDetailsPasswordService {@Autowiredprivate UserMapper userMapper;// 实现了UserDetailsPasswordService接口中的updatePassword()方法// 当系统判断密码加密方案需要升级的时候,就会自动调用updatePassword中的方法去修改数据库中的密码// 当数据库中的密码修改成功后,修改User对象的password属性,并将User对象返回@Overridepublic UserDetails updatePassword(UserDetails user, String newPassword) {Integer result = userMapper.updatePassword(user.getUsername(),newPassword);if(result==1){((User) user).setPassword(newPassword);}return user;}// 实现了UserDetailsService接口中的loadUserByUsername()方法@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {User user = userMapper.loadUserByUsername(username);if(Objects.isNull(user)){throw new UsernameNotFoundException("用户不存在");}return user;}
}
@Mapper
public interface UserMapper {User loadUserByUsername(String username);Integer updatePassword(@Param("username") String username,@Param("newPassword") String newPassword);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.imooc.uua.dao.UserMapper"><update id="updatePassword">update user set password=#{newPassword} where username=#{username}</update><select id="loadUserByUsername" resultType="com.imooc.uua.entity.User">select * from user where username=#{username};</select>
</mapper>

⑥ 配置SecurityConfig:

@EnableWebSecurity(debug = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {@AutowiredMyUserDetailsService userDetailsService;@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().anyRequest().authenticated().and().formLogin().and().csrf().disable();}@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(userDetailsService);}
}

启动项目登录成功后密码就变成了{bcrypt}$2a$10$VJeTeGpn8PhoQ6S3i4nmcuT3ho6QJpmVbtR/PFa4ffag.Dx0RFLVe

如果使用了DelegatingPasswordEncoder,只要数据库中存储的加密方案不是DelegatingPasswordEncoder中默认的BCryptPasswordEncoder,在登录成功后,都会自动升级为BCryptPasswordEncoder加密。这就是加密方案的升级。

SpringSecurity 密码加密相关推荐

  1. SpringSecurity密码加密存储

    实际项目中我们不会把密码明文存储在数据库中. ​ 默认使用的PasswordEncoder要求数据库中的密码格式为:{id}password .它会根据id去判断密码的加密方式.但是我们一般不会采用这 ...

  2. Spring Security系列教程之SpringSecurity密码加密和解密

    创建一个springboot工程导入相应坐标 <dependency><groupId>org.springframework.security</groupId> ...

  3. 【Spring框架家族】Spring--Security权限控制密码加密

    Spring Security简介 Spring Security是 Spring提供的安全认证服务的框架. 使用Spring Security可以帮助我们来简化认证 和授权的过程.官网:https: ...

  4. 一篇文章带你入门 SpringSecurity实现密码加密和解码

    文章目录 一.加密和解密 1. 为什么要加密 2. 加密方案 3. PasswordEncoder 二.前期准备 二.用户配置 1. 配置文件 2. 配置类 一.加密和解密 1. 为什么要加密 201 ...

  5. BCrypt加密怎么存入数据库_Spring Boot 中密码加密的两种姿势

    1.为什么要加密 2.加密方案 3.实践3.1 codec 加密3.2 BCryptPasswordEncoder 加密 4.源码浅析 先说一句:密码是无法解密的.大家也不要再问松哥微人事项目中的密码 ...

  6. SpringSecurity-12-PasswordEncoder密码加密简介

    SpringSecurity-12-PasswordEncoder密码加密简介 为什么密码加密? 国内的每一个开发社区在2011年发生过被黑客攻击,盗取用户信息,600多万的明文密码信息被盗取,大量用 ...

  7. Spring Boot 中密码加密的两种姿势!

    先说一句:密码是无法解密的.大家也不要再问松哥微人事项目中的密码怎么解密了! 密码无法解密,还是为了确保系统安全.今天松哥就来和大家聊一聊,密码要如何处理,才能在最大程度上确保我们的系统安全. 本文是 ...

  8. 密码加密与微服务鉴权JWT

    密码加密与微服务鉴权JWT ## 学习目标 1.用户注册时候,对数据库中用户的密码进行加密存储(使用 SpringSecurity). 2.使用 JWT 鉴权认证. 一.BCrypt 密码加密 任何应 ...

  9. Word文档使用密码加密

    Word文档使用密码加密 方法如下: 文件-->信息-->保护文档-->用密码进行加密-->设置密码

  10. 用户密码加密存储十问十答,一文说透密码安全存储

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试文章 作者 | 程序员赵鑫 来源 | cnblogs.com/xinzh ...

最新文章

  1. ros自带package在哪里_【ROS】创建ROS功能包(ROS package)
  2. 读《程序员到底怎么了?-》
  3. 关于code reiview
  4. 杭电多校(三)2019.7.29--暑假集训
  5. 【FTP】FTP 命令模式下 PASV OR PORT
  6. mongodb 导出 带条件_将 MongoDB 导出成 csv
  7. 允许使用抽象类类型 isearchboxinfo 的对象_Java学习5-设计模式+抽象类/方法
  8. 如何在Java中使ArrayList只读?
  9. python编程规则_python编程规则
  10. c++ vector常用用法总结
  11. 开启Accessibility的快捷方式-3次home键或者侧边键
  12. 【亲测】Ripro子主题美化C系列主题(春系列)-开源未加密
  13. [windows] win10下的SysMain服务
  14. Jrebel激活破解
  15. linux下virtualbox安装win7虚拟机无法调整分辨率
  16. IndentationError: unindent does not match any outer indentation level
  17. 可恢复保险丝特性测试
  18. SortPool (DGCNN) - An End-to-End Deep Learning Architecture for Graph Classification AAAI 2018
  19. Personalized Top-N Sequential Recommendation via Convolutional Sequence Embedding
  20. 《内网安全攻防:渗透测试实战指南》读书笔记(八):权限维持分析及防御

热门文章

  1. 1102: 韩信点兵
  2. oracle ogg是什么
  3. LSA和 PLSA学习笔记
  4. 007 锁存器和触发器
  5. ssh-keygen命令使用
  6. IntelliJ IDEA里面配置任何路径的时候路径里面的反斜杠分隔符变成了钱币符号
  7. 我的世界java版怎么加整合包_我的世界java如何下载安装optifine和forge及整合包和常见问题[纯小白教程]...
  8. 【Linux系列文章】磁盘、进程
  9. 保险精算笔记Chapter02
  10. 炫酷的时间HTML页面,炫酷css3垂直时间轴特效