• Spring security5.5.7出现Encoded password does not look like BCrypt异常


    目录

    背景:

    解决方案:

    BCryptPasswordEncoder介绍

    具体代码改动:


    背景:

            一个老项目,由于2022年爆发了spring bean和spring core的漏洞,将springboot从1.5.4升级到2.5.14版本,修复了spring的漏洞。同时spring security也需要同步升级,升级过程中出现了一系列错误。故做一个记录在此。

    问题:登录权限系统时,出现Encoded password does not look like BCrypt异常错误;同时报出clientSecret不匹配的问题。

    解决方案:

    统一采用PasswordEncoderFactories.createDelegatingPasswordEncoder()去获取到密码加密器PasswordEncoder

    (1)新版本的Spring Security对客户端的密钥进行了加密处理,配置中需要使用PasswordEncoderFactories.createDelegatingPasswordEncoder().encode进行加密;

    (2)登录成功后的handler,则需要采用PasswordEncoderFactories.createDelegatingPasswordEncoder().matches(clientSecret,clientDetails.getClientSecret())判断密钥是否匹配,而不是采用原有旧版的未加密的密钥进行equal进行比较字符串。

    BCryptPasswordEncoder介绍

    Spring Security 中提供了 BCryptPasswordEncoder用于用户密码的加密和验证,这里讲解一下该 PasswordEncoder 的实现逻辑.

    首先 BCryptPasswordEncoder 使用了 BCrypt 算法来对密码实现加密和验证。由于 BCrypt本身是一种 单向Hash算法,因此它和我们日常用的 MD5一样,通常情况下是无法逆向解密的。

    在 BSD系统中 BCrypt 算法主要用来替代 md5 加密算法,它使用了一种可变版本的Blowfish流密码算法。通过多次加盐和随机数,因此这套加密算法被广泛用于许多系统的密码加密当中。

    然而每次加密的结果是不一样的,如果采用两次加密的结果进行equal比较,那是得不到真实的true结果的。

    具体代码改动:

    (1)项目启动时,加载的认证服务器配置的修改

    1. @Configuration
    2. @EnableAuthorizationServer
    3. public class CchwebAuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
    4. /**
    5. * 引入BCrypt强哈希加密和解密工具
    6. */
    7. @Autowired
    8. private BCryptPasswordEncoder bcryptPasswordEncoder;
    9. /*
    10. * 客户端配置改动
    11. *
    12. * @param clients
    13. * @throws Exception
    14. */
    15. @Override
    16. public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
    17. InMemoryClientDetailsServiceBuilder builder = clients.inMemory();
    18. //spirng boot 1.5.* 升级到spring boot 2.0以上,当再次访问授权服务器时出现Encoded password does not look like BCrypt异常,需要bCryptPasswordEncoder.encode
    19. PasswordEncoder passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
    20. if (ArrayUtils.isNotEmpty(securityProperties.getOauth2().getClients())) {
    21. for (OAuth2ClientProperties config : securityProperties.getOauth2().getClients()) {
    22. //设置clientid
    23. builder.withClient(config.getClientId())
    24. // 设置clientsecret,需要加密配置
    25. .secret(passwordEncoder.encode(config.getClientSecret()))
    26. // 设置令牌过期时间,单位秒,默认7200,定义在OAuth2ClientProperties
    27. .accessTokenValiditySeconds(config.getAccessTokenValidateSeconds())
    28. // 允许的授权模式
    29. .authorizedGrantTypes("refresh_token", "authorization_code", "password")
    30. // 设置刷新令牌的过期时间,单位秒,这里设置为60天
    31. .refreshTokenValiditySeconds(5184000)
    32. // 配置oauth能获取的权限,是一个数组
    33. .scopes("all", "write", "read");
    34. }
    35. }
    36. }
    37. }

    (2)构造用户登录信息

    1. @Component
    2. public class MyUserDetailsService implements UserDetailsService {
    3. /**
    4. * 搜索用户信息,构造登录用户
    5. * @param username
    6. * @return
    7. * @throws UsernameNotFoundException
    8. */
    9. @Override
    10. public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    11. logger.info("表单登录用户名,这里的用户名是手机号:" + username);
    12. //进行权限系统登录,密码前面需要加上加密的方式,调用createDelegatingPasswordEncoder后默认会加上{bcrypt}在密码前面
    13. PasswordEncoder passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
    14. String password = passwordEncoder.encode("123456");
    15. logger.info("保存登录时间:" + nowTime);
    16. User user1 = new User(username, password,
    17. true, true, true, true,
    18. AuthorityUtils.commaSeparatedStringToAuthorityList(user.getRoles()));
    19. return user1;
    20. }
    21. }

    (3)登录成功后SuccessHandler的校验

    1. @Component("authenticationSuccessHandler")
    2. public class authenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
    3. /**
    4. * 引入BCrypt强哈希加密和解密工具
    5. */
    6. @Autowired
    7. private BCryptPasswordEncoder bcryptPasswordEncoder;
    8. /**
    9. * 此方法是用于在request中取得ClientDetails和新建tokenRequest,并用这两个参数来生成OAuth2AccessToken
    10. */
    11. @Override
    12. public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
    13. Authentication authentication) throws IOException, ServletException {
    14. //..............省略代码
    15. //..............
    16. //客户端的密钥,从header中取出
    17. String clientSecret = extractAndDecodeHeader(header, request);
    18. ClientDetails clientDetails = clientDetailsService.loadClientByClientId(clientId);
    19. PasswordEncoder passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
    20. if (clientDetails == null) {
    21. throw new UnapprovedClientAuthenticationException("clientId对应的配置信息不存在:" + clientId);
    22. } else if (!passwordEncoder.matches(clientSecret,clientDetails.getClientSecret())) { //关键是这里不能用equal匹配了
    23. throw new UnapprovedClientAuthenticationException("clientSecret不匹配:" + clientId);
    24. }
    25. //............
    26. //后续token的其他处理
    27. //............
    28. }
    29. }
  • 相关阅读:
    Matlab论文插图绘制模板第46期—帕累托图(Pareto)
    npm 实现原理
    计算机毕业设计Java文档资料管理系统(源码+系统+mysql数据库+Lw文档)
    在linux中配置固定ip
    vmware安装了centos7之后网络设置
    【ES6】函数的参数、Symbol数据类型、迭代器与生成器
    [ Linux长征路第六篇 ] Linux使用git上传gitee三板斧
    excel中怎么用乘法、加法来替代AND和OR函数
    竞赛选题 深度学习二维码识别
    Android内核模块编译
  • 原文地址:https://blog.csdn.net/kevingavinhu/article/details/125481425