zoukankan      html  css  js  c++  java
  • spring boot项目15:安全-基础使用(2)

    JAVA 8

    Spring Boot 2.5.3

    MySQL 5.7.21(单机)

    ---

    前文:

    spring boot项目14:安全-基础使用(1)

    授人以渔:

    1、Spring Boot Reference Documentation

    This document is also available as Multi-page HTML, Single page HTML and PDF.

    有PDF版本哦,下载下来!

    2、Spring Security Reference

    PDF版本哦(网页版末尾的 /html5/ 改为 /pdf/),下载下来!

    目录

    1、PasswordEncoder接口

    2、密码加密-BCryptPasswordEncoder

    3、密码加密-DelegatingPasswordEncoder

    4、使用DelegatingPasswordEncoder进行系统密码升级

    升级方式一:UpgradePasswordEncoder

    升级方式二:在DelegatingPasswordEncoder中使用UpgradePasswordEncoder2

    参考文档

    本文使用项目:

    mysql-hello

    Web项目,底层使用MySQL存储数据,默认端口30000。

    1、PasswordEncoder接口

    PasswordEncoder 是一个接口,用于密码加密,结构及公开函数如下:

    3个公开函数:

    encode 用于密码加密;

    matching 用于密码验证;

    upgradeEncoding 接口默认方法,返回Boolean,true表示密码不安全,需要使用更好的,默认返回true。来自博客园

    在所有实现类中,NoOpPasswordEncoder 被标记为 @Deprecated,原因是不安全——明文存储,源码注释中也推荐使用其它的PasswordEncoder。

    	// NoOpPasswordEncoder.java
        @Override
    	public String encode(CharSequence rawPassword) {
        	// 直接返回密码原文
    		return rawPassword.toString();
    	}

    在当前S.B.中,必须给Spring容器建立一个 PasswordEncoder 的 Bean,否则,无法登录系统:

    # 登录时错误日志
    2021-09-07 10:07:40.713 ERROR 20204 --- [io-30000-exec-3] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception
    
    java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null"
    	at org.springframework.security.crypto.password.DelegatingPasswordEncoder$UnmappedIdPasswordEncoder.matches(DelegatingPasswordEncoder.java:254) ~[spring-security-crypto-5.5.1.jar:5.5.1]
    	at org.springframework.security.crypto.password.DelegatingPasswordEncoder.matches(DelegatingPasswordEncoder.java:202) ~[spring-security-crypto-5.5.1.jar:5.5.1]
    	

    注,调试 DelegatingPasswordEncoder.matches 函数,可知详情来自博客园

    因此,前面存储明文密码时,使用了 NoOpPasswordEncoder ,但是,这是很不安全的

    使用MD5、SHA等对密码做散列,然后存储,安全性有所提高,但是,面对 反查表、彩虹表、穷举法、拖库 等破解方法时,安全性也不足够,加盐也不足够

    于是,业界发明了 BCrypt、Pbkdf2等 慢散列 算法。

    最低原则:密码必须加密后存储。在系统设计之初就要考虑到。要求更长且复杂的密码、排除常用密码(比如,123456)

    2、密码加密-BCryptPasswordEncoder

    BCrypt可以对密码做慢散列,然后存储。

    注,在面对拥有强大算力的破解者时,还是存在风险来自博客园

    BCryptVersion 有多个版本:$2a、$2y、$2b(见 BCryptVersion枚举类),默认$2a。

    BCrypt密码强度:4~31,还可以设置一个随机数字——用于加盐,默认10。

    BCryptPasswordEncoder的构造函数签名:

    // 7个
    public BCryptPasswordEncoder()
    public BCryptPasswordEncoder(int strength)
    public BCryptPasswordEncoder(BCryptVersion version)
    public BCryptPasswordEncoder(BCryptVersion version, SecureRandom random)
    public BCryptPasswordEncoder(int strength, SecureRandom random)
    public BCryptPasswordEncoder(BCryptVersion version, int strength)
    public BCryptPasswordEncoder(BCryptVersion version, int strength, SecureRandom random)

    对密码做散列后的值:

    # 强度为 12
    密码:123
    $2a$12$D8ZZt6NMhpoPTzMhZqpVIO..gzDIXr/ryuzeOFR89UzdP28s1QGoe
    密码:123456
    $2a$12$8SKvIlSNNQlI0u3GqABk2uFQ/5NygMm9rCuyERb1CUybeboDntCyG
    
    密文由4段组成:
    # $2a 版本
    # $12 强度
    # $2a$12后前22位 随机盐值
    # 最后31位 真正的散列值

    更多信息,可以看源码和官方文档。

    开始使用(在前文的基础上)...

    添加一个 BCryptPasswordEncoder Bean——注释掉前文使用的NoOpPasswordEncoder,强度12:

    	@Bean
    	PasswordEncoder passwordEncoder() {
    		PasswordEncoder encoder = new BCryptPasswordEncoder(12);
    		return encoder;
    	}

    添加用户、修改用户密码的地方,使用BCryptPasswordEncoder Bean的encode函数做加密,然后再存储:

    // 引入passwordEncoder,此时是 BCryptPasswordEncoder对象
    @Autowired
    private PasswordEncoder passwordEncoder;
    
    // 对原始密文加密
    user.setPassword(passwordEncoder.encode(dto.getPassword()));

    此时,添加用户后的密码即为 加密后 的密文,无法被还原。来自博客园

    注,用户的password使用BCryptPasswordEncoder对象加密后,用户才可以正常登录的

    情景:系统一开始存储的是明文,现在升级为使用BCryptPasswordEncoder,要怎么处理呢?

    在升级前,给用户添加一个字段encoder,记录使用的PasswordEncoder;

    然后,遍历用户表,如果用户的encoder为空或为NoOpPasswordEncoder等明文Encoder,此时,调用 BCryptPasswordEncoder Bean的encode函数对用户的明文password加密,然后,存库——修改encoder、密文password。

    注,这个只适用于 明文密码 首次升级为 密文的阶段——系统建立好后,不要等太久哦

    注,对于本文使用的 试验性项目,采取了直接修改数据库的方式来自博客园

    3、密码加密-DelegatingPasswordEncoder

    始于Spring Security 5.0,可翻译为 委派密码编码器,其自身并不直接对密码加密,而是调用其 包含的 PasswordEncoder对象 来对密码做加密。

    部分源码:

     * @author Rob Winch
     * @author Michael Simons
     * @since 5.0
     * @see org.springframework.security.crypto.factory.PasswordEncoderFactories
     */
    public class DelegatingPasswordEncoder implements PasswordEncoder {
    
    	// 密文id的前缀、后缀,包含
    	private static final String PREFIX = "{";
    
    	private static final String SUFFIX = "}";
    
    	// 默认编码器 id——idToPasswordEncoder中的一个key值
    	private final String idForEncode;
    	
    	// 默认编码器
    	private final PasswordEncoder passwordEncoderForEncode;
    
    	// 可用编码器:根据key值获取
    	private final Map<String, PasswordEncoder> idToPasswordEncoder;
    
    	// 默认验证
    	private PasswordEncoder defaultPasswordEncoderForMatches = new UnmappedIdPasswordEncoder();
        
        // 构造函数
        public DelegatingPasswordEncoder(String idForEncode, Map<String, PasswordEncoder> idToPasswordEncoder) {
        }
        
        // matches中会用到其设置的值
        public void setDefaultPasswordEncoderForMatches(PasswordEncoder defaultPasswordEncoderForMatches) {
        }
        
        @Override
        public String encode(CharSequence rawPassword) {
        }
        
        @Override
    	public boolean matches(CharSequence rawPassword, String prefixEncodedPassword) {
        }
    
    }

    使用1:构造器添加DelegatingPasswordEncoder

    添加PasswordEncoder Bean:

    @Bean
    PasswordEncoder passwordEncoder() {
    	String encodingId = "bcrypt";
    	
    	Map<String, PasswordEncoder> encoders = new HashMap<>();
    	// 前面的BCryptPasswordEncoder
    	encoders.put(encodingId, new BCryptPasswordEncoder(12));
    	// encoders还可以添加更多
    	
    	return new DelegatingPasswordEncoder(encodingId, encoders);
    }

    启动应用,测试登录:登录失败

    java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null"
    	at org.springframework.security.crypto.password.DelegatingPasswordEncoder$UnmappedIdPasswordEncoder.matches(DelegatingPasswordEncoder.java:254) ~[spring-security-crypto-5.5.1.jar:5.5.1]
    	at org.springframework.security.crypto.password.DelegatingPasswordEncoder.matches(DelegatingPasswordEncoder.java:202) ~[spring-security-crypto-5.5.1.jar:5.5.1]

    失败原因:

    之前数据库中,密码使用 BCryptPasswordEncoder 加密得到的,没有添加 前缀、后置——“{}”。来自博客园

    解决方法:给旧密码添加 {bcrypt}

    mysql> update app_user set password="{bcrypt}$2a$12$D8ZZt6NMhpoPTzMhZqpVIO..gzDIXr/ryuzeOFR89UzdP28s1QGoe" where id =1;
    Query OK, 1 row affected (0.01 sec)
    Rows matched: 1  Changed: 1  Warnings: 0

    再次测试:登录成功

    注,真实项目中肯定不能这么直接改库的

    测试添加用户:下图的 id=10 为刚刚添加的用户,默认就有了 {bcrypt} 了。测试登录,成功。来自博客园

    第二种创建DelegatingPasswordEncoder的方式:PasswordEncoderFactories

    @Bean
    PasswordEncoder passwordEncoder() {
    	PasswordEncoder encoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
    	return encoder;
    }

    createDelegatingPasswordEncoder()源码:

    默认使用 BCryptPasswordEncoder()——可认为是Spring Security推荐,但没有指定强度,这样的话,前面的用户的密码应该不能使用。

    还包含其它各种PasswordEncoder,但不是 encode时使用的,可以用在调用 matches时。

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

    测试之前的用户登录:居然登录成功了!不是没有指定强度吗?

    测试添加用户:添加成功。新用户 u21090702 的 password的 BCrypt的强度的确是10。但是,调用matches时,会对强度做区分,因此,才会登录成功。

    4、使用DelegatingPasswordEncoder进行系统密码升级

    明文密码 升级为 密文密码,前面说了,使用 跑库 的方式,一个一个地使用当前系统加密方式更新即可。

    如果系统已经是使用 算法A 加密了密码,此时,升级为 算法2,这就需要 实现自己的PasswordEncoder来实现——但只能更新登录用户的密码密文。

    当然,实现自己的PasswordEncoder,也可以实现明文升级到密文阶段。来自博客园

    更新(升级)密码密文流程:

    用户登录,带着密码明文;

    使用 旧算法的matches,匹配成功,此时,使用 新算法encode密码明文,得到新的密文,再修改用户的password为新的;

    使用 旧算法的matches,匹配失败,使用新算法matches,匹配成功,无需更新;来自博客园

    要是新旧算法都匹配失败,用户登录失败

    以前面的数据为例:

    id=2、7、9的password 是使用 BCryptPasswordEncoder encode得到,此时,需要升级为 DelegatingPasswordEncoder 中 使用BCryptPasswordEncoder 的格式。

    目标是,用户2、7、9登录后,更新其password 为使用 DelegatingPasswordEncoder 的格式。

    mysql> select id,username,password,create_time from app_user;
    +----+-----------+----------------------------------------------------------------------+---------------------+
    | id | username  | password                                                             | create_time         |
    +----+-----------+----------------------------------------------------------------------+---------------------+
    |  1 | admin     | {bcrypt}$2a$12$D8ZZt6NMhpoPTzMhZqpVIO..gzDIXr/ryuzeOFR89UzdP28s1QGoe | 2021-09-05 09:48:04 |
    |  2 | user      | $2a$12$D8ZZt6NMhpoPTzMhZqpVIO..gzDIXr/ryuzeOFR89UzdP28s1QGoe         | 2021-09-05 09:50:53 |
    |  7 | lib       | $2a$12$8SKvIlSNNQlI0u3GqABk2uFQ/5NygMm9rCuyERb1CUybeboDntCyG         | 2021-09-05 13:46:03 |
    |  9 | nextyear  | $2a$12$8SKvIlSNNQlI0u3GqABk2uFQ/5NygMm9rCuyERb1CUybeboDntCyG         | 2021-09-05 13:57:43 |
    | 10 | u21090701 | {bcrypt}$2a$12$yF8p3xNRvUaVFf0fdkWMHO.mfvOTjsOBzzCYhEb2aC0BYZ6sV0NHm | 2021-09-07 11:44:41 |
    | 11 | u21090702 | {bcrypt}$2a$10$/ZR8PHq6gVK1o5/kqj7Mq.vRAktbfD3POgz6wJu7CJP.U0zT8m7NW | 2021-09-07 11:59:33 |
    +----+-----------+----------------------------------------------------------------------+---------------------+
    6 rows in set (0.00 sec)
    

    升级方式一:UpgradePasswordEncoder

    注销AppWebSecurityConfig中的PasswordEncoder Bean生成函数;来自博客园

    添加自定义UpgradePasswordEncoder:

    package org.lib.mysqlhello.security;
    
    import javax.servlet.http.HttpServletRequest;
    
    import org.lib.mysqlhello.security.self.AppUser;
    import org.lib.mysqlhello.security.self.AppUserDAO;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
    import org.springframework.security.crypto.factory.PasswordEncoderFactories;
    import org.springframework.security.crypto.password.PasswordEncoder;
    import org.springframework.stereotype.Component;
    
    import lombok.extern.slf4j.Slf4j;
    
    /**
     * 密码加密算法升级
     * @author ben
     * @date 2021-09-07 13:52:07 CST
     */
    @Component
    @Slf4j
    public class UpgradePasswordEncoder implements PasswordEncoder {
    
    	private BCryptPasswordEncoder oldEncoder = new BCryptPasswordEncoder(12);
    	
    	private PasswordEncoder newEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();;
    	
    	@Autowired
    	private AppUserDAO appUserDao;
    	
    	@Autowired
    	private HttpServletRequest req;
    	
    	@Override
    	public String encode(CharSequence rawPassword) {
    		return newEncoder.encode(rawPassword);
    	}
    
    	@Override
    	public boolean matches(CharSequence rawPassword, String encodedPassword) {
    		if (rawPassword == null) {
    			throw new IllegalArgumentException("rawPassword cannot be null");
    		}
    		if (encodedPassword == null || encodedPassword.length() == 0) {
    			return false;
    		}
    		
    		if (oldEncoder.matches(rawPassword, encodedPassword)) {
    			// 怎么获取用户并更新?用户ID哪里找?TODO
    			// 根据session去获取用户信息?TODO
    			String username = req.getParameter("username");
    			log.info("旧密码算法匹配成功:username={}", username);
    			// 旧密码 匹配成功!
    			// 更新数据库
    			String newPassword = newEncoder.encode(rawPassword);
    			System.out.println("newPassword=" + newPassword);
    			AppUser user = appUserDao.findByUsername(username);
    			System.out.println("user1=" + user);
    			// 更新
    			user.setPassword(newPassword);
    			appUserDao.save(user);
    			System.out.println("user2=" + user);
    			log.warn("升级了用户密码加密算法:username={}", username);
    			return true;
    		}
    		
    		if (newEncoder.matches(rawPassword, encodedPassword)) {
    			// 新密码匹配成功
    			String username = req.getParameter("username");
    			log.info("新密码算法匹配成功:username={}", username);
    			return true;
    		}
    		
    		// 新旧密码算法都匹配失败,登录失败
    		return false;
    	}
    
    }
    

    旧密码算法匹配:升级;新密码算法匹配:直接返回true;都不匹配:登录失败。来自博客园

    升级后,使用 已升级、未升级 的用户登录:

    两种用户登录成功,数据库的密码也得到了更新——升级。完成。

    疑问:这里的用户获取方式是根据登录参数中的username,还没有判断是否是登录过程(/login)调用呢?matches会在什么情况下登录呢?来自博客园

    升级方式二:在DelegatingPasswordEncoder中使用UpgradePasswordEncoder2

    方式一中,UpgradePasswordEncoder包含了DelegatingPasswordEncoder,本节反过来,使用 DelegatingPasswordEncoder 包装 UpgradePasswordEncoder2。

    升级为 使用 Pbkdf2PasswordEncoder:

    @EnableWebSecurity
    public class AppWebSecurityConfig extends WebSecurityConfigurerAdapter {
    
    // ...
    
    	/**
    	 * 使用DelegatingPasswordEncoder(3):包装UpgradePasswordEncoder2
    	 * @author ben
    	 * @date 2021-09-07 14:18:22 CST
    	 * @return
    	 */
    	@Bean
    	PasswordEncoder passwordEncoder() {
    		// 这里还是旧的,但UpgradePasswordEncoder2中使用新的
    		String encodingId = "Pbkdf2";
    		
    		Map<String, PasswordEncoder> encoders = new HashMap<>();
    		// 新的、默认密码解析器
    		encoders.put(encodingId, new Pbkdf2PasswordEncoder());
    		// encoders还可以添加更多
    		
    		DelegatingPasswordEncoder encoder = new DelegatingPasswordEncoder(encodingId, encoders);
    		
    		// 设置升级PasswordEncoder:影响matches
    		encoder.setDefaultPasswordEncoderForMatches(new UpgradePasswordEncoder2(appUserDao, req));
    		
    		return encoder;
    	}
        
    }

    UpgradePasswordEncoder2:

    旧加密算法 BCryptPasswordEncoder,新加密算法 Pbkdf2PasswordEncoder。来自博客园

    package org.lib.mysqlhello.security;
    
    import java.util.HashMap;
    import java.util.Map;
    
    import javax.servlet.http.HttpServletRequest;
    
    import org.lib.mysqlhello.security.self.AppUser;
    import org.lib.mysqlhello.security.self.AppUserDAO;
    import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
    import org.springframework.security.crypto.password.DelegatingPasswordEncoder;
    import org.springframework.security.crypto.password.PasswordEncoder;
    import org.springframework.security.crypto.password.Pbkdf2PasswordEncoder;
    
    import lombok.extern.slf4j.Slf4j;
    
    /**
     * 密码加密算法升级(2)
     * @author ben
     * @date 2021-09-07 13:52:07 CST
     */
    @Slf4j
    public class UpgradePasswordEncoder2 implements PasswordEncoder {
    	
    	public UpgradePasswordEncoder2(AppUserDAO appUserDao, HttpServletRequest req) {
    		this.appUserDao = appUserDao;
    		this.req = req;
    	}
    	
    	// 旧加密算法
    	private BCryptPasswordEncoder oldEncoder = new BCryptPasswordEncoder(12);
    	
    	// newEncoder 不能使用 DelegatingPasswordEncoder!matches会匹配异常!
    //	private static DelegatingPasswordEncoder newEncoder = (DelegatingPasswordEncoder) PasswordEncoderFactories.createDelegatingPasswordEncoder();
    	private Pbkdf2PasswordEncoder newEncoder = new Pbkdf2PasswordEncoder();
    
    	private AppUserDAO appUserDao;
    	
    	private HttpServletRequest req;
    	
    	@Override
    	public String encode(CharSequence rawPassword) {
    		return newEncoder.encode(rawPassword);
    	}
    
    	@Override
    	public boolean matches(CharSequence rawPassword, String encodedPassword) {
    		if (rawPassword == null) {
    			throw new IllegalArgumentException("rawPassword cannot be null");
    		}
    		if (encodedPassword == null || encodedPassword.length() == 0) {
    			return false;
    		}
    		
    		System.out.println("
    rawPassword=" + rawPassword);
            // 这里的 encodedPassword没有前缀!
    		System.out.println("encodedPassword=" + encodedPassword);
    		
    		if (oldEncoder.matches(rawPassword, encodedPassword)) {
    			// 怎么获取用户并更新?用户ID哪里找?TODO
    			// 根据session去获取用户信息?TODO
    			String username = req.getParameter("username");
    			log.info("旧密码算法匹配成功:username={}", username);
    			// 旧密码 匹配成功!
    			// 更新数据库
    			String newPassword = this.encode(rawPassword);
    			AppUser user = appUserDao.findByUsername(username);
    			user.setPassword(newPassword);
    			appUserDao.save(user);
    			log.warn("升级了用户密码加密算法:username={}", username);
    			return true;
    		}
    		
    		// 嵌套到 DelegatingPasswordEncoder 中使用时,这里的 encodedPassword 没有前缀,会导致匹配失败!
    		if (newEncoder.matches(rawPassword, encodedPassword)) {
    			// 新密码匹配成功
    			String username = req.getParameter("username");
    			log.info("新密码算法匹配成功:username={}", username);
    			return true;
    		}
    		
    		// 新旧密码算法都匹配失败,登录失败
    		return false;
    	}
    	
    }
    

    将之前的用户的密码设置为 使用 BCryptPasswordEncoder 加密(明文密码 123456):

    mysql> update app_user set password="$2a$12$8SKvIlSNNQlI0u3GqABk2uFQ/5NygMm9rCuyERb1CUybeboDntCyG" where id in (1,2);
    Query OK, 2 rows affected (0.02 sec)
    Rows matched: 2  Changed: 2  Warnings: 0
    mysql> select id,username,password from app_user;
    +----+----------+--------------------------------------------------------------+
    | id | username | password                                                     |
    +----+----------+--------------------------------------------------------------+
    |  1 | admin    | $2a$12$8SKvIlSNNQlI0u3GqABk2uFQ/5NygMm9rCuyERb1CUybeboDntCyG |
    |  2 | user     | $2a$12$8SKvIlSNNQlI0u3GqABk2uFQ/5NygMm9rCuyERb1CUybeboDntCyG |
    +----+----------+--------------------------------------------------------------+
    2 rows in set (0.00 sec)

    使用旧用户-admin-登录:正确的用户名和密码,登录成功,登录后,password字段改变。来自博客园

    mysql> select id,username,password from app_user;
    +----+----------+----------------------------------------------------------------------------------+
    | id | username | password                                                                         |
    +----+----------+----------------------------------------------------------------------------------+
    |  1 | admin    | 7ae36a28ef784970ed4da3789d3fa4911a9fc8b0477b0b4716ab2ed695c7e442d3b0b0981ec0e13d |
    |  2 | user     | $2a$12$8SKvIlSNNQlI0u3GqABk2uFQ/5NygMm9rCuyERb1CUybeboDntCyG                     |
    +----+----------+----------------------------------------------------------------------------------+
    2 rows in set (0.00 sec)
    

    新增用户:成功,但其password前方多了 前缀——使用的DelegatingPasswordEncoder导致。来自博客园

    mysql> select id,username,password from app_user;
    +----+-----------+------------------------------------------------------------------------------------------+
    | id | username  | password                                                                                 |
    +----+-----------+------------------------------------------------------------------------------------------+
    |  1 | admin     | 7ae36a28ef784970ed4da3789d3fa4911a9fc8b0477b0b4716ab2ed695c7e442d3b0b0981ec0e13d         |
    |  2 | user      | $2a$12$8SKvIlSNNQlI0u3GqABk2uFQ/5NygMm9rCuyERb1CUybeboDntCyG                             |
    | 13 | u21090701 | {Pbkdf2}99756f46bf52d31ec4ab11c8ee40f1c53628eb58298b6c548b3aa6db9b4028996427b0a724156c10 |
    +----+-----------+------------------------------------------------------------------------------------------+
    3 rows in set (0.00 sec)

    使用 新增用户登录:登录成功。

    使用错误密码登录时,产生异常:以user为例

    rawPassword=222
    encodedPassword=$2a$12$8SKvIlSNNQlI0u3GqABk2uFQ/5NygMm9rCuyERb1CUybeboDntCyG
    2021-09-07 16:00:06.957 ERROR 22712 --- [io-30000-exec-7] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : 
    Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception
    
    java.lang.IllegalArgumentException: Detected a Non-hex character at 1 or 2 position
    	at org.springframework.security.crypto.codec.Hex.decode(Hex.java:58) ~[spring-security-crypto-5.5.1.jar:5.5.1]
    	at org.springframework.security.crypto.password.Pbkdf2PasswordEncoder.decode(Pbkdf2PasswordEncoder.java:193) ~[spring-security-crypto-5.5.1.jar:5.5.1]
    	at org.springframework.security.crypto.password.Pbkdf2PasswordEncoder.matches(Pbkdf2PasswordEncoder.java:184) ~[spring-security-crypto-5.5.1.jar:5.5.1]

    问题发生在 Pbkdf2PasswordEncoder 执行matches时——这个Encoder会产生异常。可能需要捕捉,然后返回 false。来自博客园

    解决新增用户和就用户升级密码后 有无前缀{Pbkdf2} 的问题:修改UpgradePasswordEncoder2中的matches——更新用户password部分

    			// 加前缀!
    			String newPassword = "{" + ENCODER_ID + "}" + this.encode(rawPassword);
    			user.setPassword(newPassword);

    此时使用旧密码用户登录,会执行升级密码:

    有无前缀的区别:

    有前缀,就使用DelegatingPasswordEncoder 的 matches执行——找到PasswordEncoder;来自博客园

    没有前缀,则使用默认的 defaultPasswordEncoderForMatches 执行;

    	// DelegatingPasswordEncoder.java 的 matches函数
        @Override
    	public boolean matches(CharSequence rawPassword, String prefixEncodedPassword) {
    		if (rawPassword == null && prefixEncodedPassword == null) {
    			return true;
    		}
    		String id = extractId(prefixEncodedPassword);
    		PasswordEncoder delegate = this.idToPasswordEncoder.get(id);
    		if (delegate == null) {
    			return this.defaultPasswordEncoderForMatches.matches(rawPassword, prefixEncodedPassword);
    		}
    		String encodedPassword = extractEncodedPassword(prefixEncodedPassword);
    		return delegate.matches(rawPassword, encodedPassword);
    	}

    》》》全文完《《《

    密码加密存储了,系统用起来更加放心了。

    但是,简单密码还是要避免,HTTPS也是必须的。

    加密、安全,系统方面尽最大努力保证安全。来自博客园

    要是用户方面出现泄露,尽量减少用户损失吧,比如,被盗号了,然后,异常删除用户所有数据。用户发现后,是不是要给用户找回来?

    本文和前文 仅涉及 密码的保护,更高级的保护措施呢?OAuth2、Token方式?还需继续研究才是...来自博客园

    参考文档

    1、《Spring Security实战》

    书,作者:陈木鑫,2019年8月第1版

    非常感谢。

    2、

  • 相关阅读:
    Designing With Web Standard(一)
    再听姜育恒
    终于找到Effective C Sharp电子版了
    继续下一个题目
    想做就做,要做得漂亮
    空悲还是空杯
    整理,中庸
    分布式系统设计随想
    日志log4
    log4net更换目录
  • 原文地址:https://www.cnblogs.com/luo630/p/15237025.html
Copyright © 2011-2022 走看看