密码加密 常见的几种密码加密的方式
BCryptPasswordEncoder
BCryptPasswordEncoder 使用 bcrypt 算法对密码进行加密,为了提高密码的安全性,bcrypt算法故意降低运行速度,以增强密码破解的难度。同时 BCryptP asswordEncoder “为自己带盐”开发者不需要额外维护一个“盐” 字段,使用 BCryptPasswordEncoder 加密后的字符串就已经“带盐”了,即使相同的明文每次生成的加密字符串都不相同。
Argon2PasswordEncoder
Argon2PasswordEncoder 使用 Argon2 算法对密码进行加密,Argon2 曾在 Password Hashing Competition 竞赛中获胜。为了解决在定制硬件上密码容易被破解的问题,Argon2也是故意降低运算速度,同时需要大量内存,以确保系统的安全性。
Pbkdf2PasswordEncoder
Pbkdf2PasswordEncoder 使用 PBKDF2 算法对密码进行加密,和前面几种类似,PBKDF2
算法也是一种故意降低运算速度的算法,当需要 FIPS (Federal Information Processing Standard,美国联邦信息处理标准)认证时,PBKDF2 算法是一个很好的选择。
SCryptPasswordEncoder
SCryptPasswordEncoder 使用scrypt 算法对密码进行加密,和前面的几种类似,serypt 也是一种故意降低运算速度的算法,而且需要大量内存。
PasswordEncoder 源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 package org.springframework.security.crypto.password;public interface PasswordEncoder { String encode (CharSequence rawPassword) ; boolean matches (CharSequence rawPassword, String encodedPassword) ; default boolean upgradeEncoding (String encodedPassword) { return false ; } }
从中我们得出了他的三个实现方法
encode 用来进行明文加密的
matches 用来比较密码的方法
upgradeEncoding 用来给密码进行升级的方法
DelegatingPasswordEncoder 根据上面 PasswordEncoder的介绍,可能会以为 Spring security 中默认的密码加密方案应该是四种自适应单向加密函数中的一种,其实不然,
在 spring Security 5.0之后,默认的密码加密方案其实是 DelegatingPasswordEncoder。 它继承了上面的PasswordEcoder,并且实现了更为强大的功能
同时它具有更好的兼容性 和 便捷性 以及 安全性
DelegatingPasswordEncoder
源码文档1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 于前缀标识符委派给另一个密码编码器的密码编码器。 您可以使用 轻松 org.springframework.security.crypto.factory.PasswordEncoderFactories构造实例。或者,您可以创建自己的自定义实例。例如: String idForEncode = "bcrypt" ; Map encoders = new HashMap <>(); encoders.put(idForEncode, new BCryptPasswordEncoder ()); encoders.put("noop" , NoOpPasswordEncoder.getInstance()); encoders.put("pbkdf2" , new Pbkdf2PasswordEncoder ()); encoders.put("scrypt" , new SCryptPasswordEncoder ()); encoders.put("sha256" , new StandardPasswordEncoder ()); PasswordEncoder passwordEncoder = new DelegatingPasswordEncoder (idForEncode, encoders); 密码的一般格式为: {id}encodedPassword 这样,“id”是用于查找 PasswordEncoder 应使用的标识符,而“encodedPassword”是所选 PasswordEncoder的原始编码密码。“id”必须位于密码的开头,以“{”开头,以“}”结尾。如果找不到“id”,则“id”将为空。例如,以下内容可能是使用不同“id”编码的密码列表。所有原始密码都是“密码”。 {bcrypt}$2a$10 $dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG {noop}password {pbkdf2}5d923b44a6d129f3ddf3e3c8d29412723dcbde72445e8ef6bf3b508fbf17fa4ed4d6b99ca763d8dc {scrypt}$e0801$8bWJaSu2IKSn9Z9kM+TPXfOc/9bdYSrN1oD9qfVThWEwdRTnO7re7Ei+fUZRJ68k9lTyuTeUp4of4g24hHnazw==$OAOec05+bXxvuu/1qZ6NUR+xQYvYv7BeL1QxwRpY5Pc= {sha256}97cde38028ad898ebc02e690819fa220e88c62e0699403e94fff291cfffaf8410849f27605abcbc0 对于我们上面构建的委派密码编码器: 第一个密码的 PasswordEncoder ID为“bcrypt”,编码密码为“$2a$10 $dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG”。匹配时,它将委托给 org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder 第二个密码的ID为“noop”,编码密码为 PasswordEncoder “password”。匹配时,它将委托给 NoOpPasswordEncoder 第三个密码的 PasswordEncoder ID为“pbkdf2”,编码密码为“5d923b44a6d129f3ddf3e3c8d29412723dcbde72445e8ef6bf3b508fbf17fa4ed4d6b99ca763d8dc”。匹配时,它将委托给 Pbkdf2PasswordEncoder 第四个密码的 PasswordEncoder ID为“scrypt”,编码密码为“$e 0801$8bWJaSu2IKSn9Z9kM+TPXfOc/9bdYSrN1oD9qfVThWEwdRTnO7re7Ei+fUZRJ68k9lTyuTeUp4of4g24hHnazw==$OAOec 05 +bXxvuu/1qZ6NUR+xQYvYv7BeL1QxwRpY5Pc=” 匹配时,它将委托给 org.springframework.security.crypto.scrypt.SCryptPasswordEncoder 最终密码的 ID 为 PasswordEncoder “sha256”,编码密码为“97cde38028ad898ebc02e690819fa220e88c62e0699403e94fff291cfffaf8410849f27605abcbc0”。匹配时,它将委托给 StandardPasswordEncoder 传递给构造函数的内容 idForEncode 确定将 PasswordEncoder 用于编码密码。在我们上面构造的中 DelegatingPasswordEncoder ,这意味着编码“密码”的结果将被委托给 BCryptPasswordEncoder 并以“{bcrypt}”为前缀。最终结果如下所示: {bcrypt}$2a$10 $dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG 匹配是基于“id”和“id” PasswordEncoder 到构造函数中提供的映射完成的。我们在“密码存储格式”中的示例提供了如何完成此操作的工作示例。默认情况下,使用未映射的“id”(包括空 id)的密码调用的结果 matches(CharSequence, String) 将导致 IllegalArgumentException.可以使用 自定义 setDefaultPasswordEncoderForMatches(PasswordEncoder)此行为。 请参阅: org.springframework.security.crypto.factory.PasswordEncoderFactories
DelegatingPasswordEncoder
源码解读1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 public class DelegatingPasswordEncoder implements PasswordEncoder { private static final String PREFIX = "{" ; private static final String SUFFIX = "}" ; private final String idForEncode; private final PasswordEncoder passwordEncoderForEncode; private final Map<String, PasswordEncoder> idToPasswordEncoder; private PasswordEncoder defaultPasswordEncoderForMatches = new UnmappedIdPasswordEncoder (); public DelegatingPasswordEncoder (String idForEncode, Map<String, PasswordEncoder> idToPasswordEncoder) { if (idForEncode == null ) { throw new IllegalArgumentException ("idForEncode cannot be null" ); } if (!idToPasswordEncoder.containsKey(idForEncode)) { throw new IllegalArgumentException ("idForEncode " + idForEncode + "is not found in idToPasswordEncoder " + idToPasswordEncoder); } for (String id : idToPasswordEncoder.keySet()) { if (id == null ) { continue ; } if (id.contains(PREFIX)) { throw new IllegalArgumentException ("id " + id + " cannot contain " + PREFIX); } if (id.contains(SUFFIX)) { throw new IllegalArgumentException ("id " + id + " cannot contain " + SUFFIX); } } this .idForEncode = idForEncode; this .passwordEncoderForEncode = idToPasswordEncoder.get(idForEncode); this .idToPasswordEncoder = new HashMap <>(idToPasswordEncoder); }
实现 自定义配置文件实现PasswordEncoder
接口 三个方法前面有介绍
1 2 3 4 5 6 7 8 9 10 11 12 @Override public String encode(CharSequence rawPassword) { return null; } @Override public boolean matches(CharSequence rawPassword, String encodedPassword) { return false; } @Override public boolean upgradeEncoding(String encodedPassword) { return PasswordEncoder.super.upgradeEncoding(encodedPassword); }
密码自动升级 推荐使用DelegatingPasswordEncoder 的另外一个好处就是自动进行密码加密方案的升级,这个功能在整合一些老的系统时非常有用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 CREATE TABLE `user `( `id` int (11 ) NOT NULL AUTO_INCREMENT, `username` varchar (32 ) DEFAULT NULL , `password` varchar (255 ) DEFAULT NULL , `enabled` tinyint(1 ) DEFAULT NULL , `accountNonExpired` tinyint(1 ) DEFAULT NULL , `accountNonLocked` tinyint(1 ) DEFAULT NULL , `credentialsNonExpired` tinyint(1 ) DEFAULT NULL , PRIMARY KEY (`id`) ) ENGINE= InnoDB AUTO_INCREMENT= 4 DEFAULT CHARSET= utf8; CREATE TABLE `role`( `id` int (11 ) NOT NULL AUTO_INCREMENT, `name` varchar (32 ) DEFAULT NULL , `name_zh` varchar (32 ) DEFAULT NULL , PRIMARY KEY (`id`) ) ENGINE= InnoDB AUTO_INCREMENT= 4 DEFAULT CHARSET= utf8; CREATE TABLE `user_role`( `id` int (11 ) NOT NULL AUTO_INCREMENT, `uid` int (11 ) DEFAULT NULL , `rid` int (11 ) DEFAULT NULL , PRIMARY KEY (`id`), KEY `uid` (`uid`), KEY `rid` (`rid`) ) ENGINE= InnoDB AUTO_INCREMENT= 5 DEFAULT CHARSET= utf8;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 BEGIN ; INSERT INTO `user ` VALUES (1 , 'root' , '{noop}123' , 1 , 1 , 1 , 1 ); INSERT INTO `user ` VALUES (2 , 'admin' , '{noop}123' , 1 , 1 , 1 , 1 ); INSERT INTO `user ` VALUES (3 , 'blr' , '{noop}123' , 1 , 1 , 1 , 1 ); COMMIT ;BEGIN ; INSERT INTO `role` VALUES (1 , 'ROLE_product' , '商品管理员' ); INSERT INTO `role` VALUES (2 , 'ROLE_admin' , '系统管理员' ); INSERT INTO `role` VALUES (3 , 'ROLE_user' , '用户管理员' ); COMMIT ;BEGIN ; INSERT INTO `user_role` VALUES (1 , 1 , 1 ); INSERT INTO `user_role` VALUES (2 , 1 , 2 ); INSERT INTO `user_role` VALUES (3 , 2 , 2 ); INSERT INTO `user_role` VALUES (4 , 3 , 3 ); COMMIT ;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <dependency > <groupId > mysql</groupId > <artifactId > mysql-connector-java</artifactId > <version > 5.1.38</version > </dependency > <dependency > <groupId > org.mybatis.spring.boot</groupId > <artifactId > mybatis-spring-boot-starter</artifactId > <version > 2.2.0</version > </dependency > <dependency > <groupId > com.alibaba</groupId > <artifactId > druid</artifactId > <version > 1.2.8</version > </dependency >
1 2 3 4 5 6 7 8 spring.datasource.type =com.alibaba.druid.pool.DruidDataSource spring.datasource.driver-class-name =com.mysql.jdbc.Driver spring.datasource.url =jdbc:mysql://localhost:3306/security?characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false spring.datasource.username =root spring.datasource.password =root mybatis.mapper-locations =classpath:/mapper/*.xml mybatis.type-aliases-package =com.baizhi.entity logging.level.com.baizhi.dao =debug
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 public class User implements UserDetails { private Integer id; private String username; private String password; private Boolean enabled; private Boolean accountNonExpired; private Boolean accountNonLocked; private Boolean credentialsNonExpired; private List<Role> roles = new ArrayList <>(); @Override public Collection<? extends GrantedAuthority > getAuthorities() { List<SimpleGrantedAuthority> authorities = new ArrayList <>(); for (Role role : roles) { authorities.add(new SimpleGrantedAuthority (role.getName())); } return authorities; } @Override public String getPassword () { return password; } public void setPassword (String password) { this .password = password; } @Override public String getUsername () { return username; } public void setUsername (String username) { this .username = username; } @Override public boolean isAccountNonExpired () { return accountNonExpired; } public void setAccountNonExpired (Boolean accountNonExpired) { this .accountNonExpired = accountNonExpired; } @Override public boolean isAccountNonLocked () { return accountNonLocked; } public void setAccountNonLocked (Boolean accountNonLocked) { this .accountNonLocked = accountNonLocked; } @Override public boolean isCredentialsNonExpired () { return credentialsNonExpired; } public void setCredentialsNonExpired (Boolean credentialsNonExpired) { this .credentialsNonExpired = credentialsNonExpired; } @Override public boolean isEnabled () { return enabled; } public void setEnabled (Boolean enabled) { this .enabled = enabled; } public void setRoles (List<Role> roles) { this .roles = roles; } public Integer getId () { return id; } public void setId (Integer id) { this .id = id; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 public class Role { private Integer id; private String name; private String nameZh; public Integer getId () { return id; } public void setId (Integer id) { this .id = id; } public String getName () { return name; } public void setName (String name) { this .name = name; } public String getNameZh () { return nameZh; } public void setNameZh (String nameZh) { this .nameZh = nameZh; } }
1 2 3 4 5 6 @Mapper public interface UserDao { List<Role> getRolesByUid (Integer uid) ; User loadUserByUsername (String username) ; Integer updatePassword (@Param("username") String username,@Param("password") String password) ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace ="com.baizhi.dao.UserDao" > <select id ="loadUserByUsername" resultType ="User" > select id, username, password, enabled, accountNonExpired, accountNonLocked, credentialsNonExpired from `user` where username = #{username} </select > <select id ="getRolesByUid" resultType ="Role" > select r.id, r.name, r.name_zh nameZh from `role` r, `user_role` ur where r.id = ur.rid and ur.uid = #{uid} </select > <update id ="updatePassword" > update `user` set password=#{password} where username=#{username} </update > </mapper >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 @Service public class MyUserDetailService implements UserDetailsService ,UserDetailsPasswordService { private final UserDao userDao; @Autowired public MyUserDetailService (UserDao userDao) { this .userDao = userDao; } @Override public UserDetails loadUserByUsername (String username) throws UsernameNotFoundException { User user = userDao.loadUserByUsername(username); if (ObjectUtils.isEmpty(user)) { throw new RuntimeException ("用户不存在!" ); } user.setRoles(userDao.getRolesByUid(user.getId())); return user; } @Override public UserDetails updatePassword (UserDetails user, String newPassword) { Integer result = userDao.updatePassword(user.getUsername(), newPassword); if (result == 1 ) { ((User) user).setPassword(newPassword); } return user; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { private final MyUserDetailService myUserDetailService; @Autowired public SecurityConfig (MyUserDetailService myUserDetailService) { this .myUserDetailService = myUserDetailService; } @Override protected void configure (AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(myUserDetailService); } }
说明 部分的代码是由@编程不良人
的学习教程中提供的,仅作为自己的学习参考使用!
作者教程链接 : Spring Security 最新实战教程