2023-06-18
原文作者:代码有毒 mrcode 原文地址:https://mrcode.blog.csdn.net/article/details/81502532

自定义用户认证逻辑

  • 处理用户信息获取逻辑
  • 处理用户校验逻辑
  • 处理密码加密解密

注意!注意!

视频中启动的是demo。而这次要写的代码在security-browser中。
遇到一个问题也就是在 项目结构 一文中最后的 关于依赖项目没有被扫描到的问题;
如果按照笔记走出现了这个问题。就去项目结构中关于”关于依赖项目没有被扫描到的问题”解决

处理用户信息获取逻辑

    org.springframework.security.core.userdetails.UserDetailsService

UserDetailsService接口用于加载用户特定的数据,它在整个框架中作为用户DAO使用,是验证提供者使用的策略。
该接口只需要一个只读方法,这简化了对新的数据访问策略的支持。

实现一个自定义的UserDetailsService

    package cn.mrcode.imooc.springsecurity.securitybrowser;
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.security.core.authority.AuthorityUtils;
    import org.springframework.security.core.userdetails.User;
    import org.springframework.security.core.userdetails.UserDetails;
    import org.springframework.security.core.userdetails.UserDetailsService;
    import org.springframework.security.core.userdetails.UsernameNotFoundException;
    import org.springframework.stereotype.Component;
    
    /**
     * ${desc}
     * @author zhuqiang
     * @version 1.0.1 2018/8/3 9:16
     * @date 2018/8/3 9:16
     * @since 1.0
     */
    // 自定义数据源来获取数据
    // 这里只要是存在一个自定义的 UserDetailsService ,那么security将会使用该实例进行配置
    @Component
    public class MyUserDetailsService implements UserDetailsService {
        Logger logger = LoggerFactory.getLogger(getClass());
    
        // 可以从任何地方获取数据
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
            // 根据用户名查找用户信息
            logger.info("登录用户名", username);
            // 写死一个密码,赋予一个admin权限
            return new User(username, "123456",
                            AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
        }
    }

这样就能让自定义的UserdetailsService生效了。但是在浏览器中登录的时候,后台报错了

    java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null"

这个异常是spring security5+后密码策略变更了。必须使用 PasswordEncoder 方式也就是你存储密码的时候
需要使用{noop}123456这样的方式。这个在官网文档中有讲到

花括号里面的是encoder id ,这个支持的全部列表在以下的方法中定义

    org.springframework.security.crypto.factory.PasswordEncoderFactories#createDelegatingPasswordEncoder

noop 对应的处理类是org.springframework.security.crypto.password.NoOpPasswordEncoder,只用于测试;因为没有做任何加密功能

修改完成之后再次访问。发现可以了

小插曲

之前由于跳过了视频中的项目结构解说,自己根据经验使用gradle。由于视频中用的maven。我用的gradle。问题还真不少;

视频中演示 demo项目依赖于我们刚刚配置好的security-browser项目中的自定义UserDetailsService功能;
启动demo项目后发现browser中的配置无法被扫描到;然后重新观看了项目结构这一章节。并把该章节笔记重新记录下了;
有可能会导致一小部分依赖配置和这之前的笔记中有一点点的不一样。不过不要紧。已经解决了

处理用户校验逻辑

自定义的其他逻辑是在 org.springframework.security.core.userdetails.User 中提供的,
只要在登录的时候把user中提供的信息返回即可达到支持的业务逻辑,下面列出支持的业务场景:

  • isEnabled 账户是否启用
  • isAccountNonExpired 账户没有过期
  • isCredentialsNonExpired 身份认证是否是有效的
  • isAccountNonLocked 账户没有被锁定
    对于 isAccountNonLocked 和 isEnabled 没有做业务处理,只是抛出了对于的异常信息;
    // 这里的几个布尔值的含义对应上面列出来的顺序
    User admin = new User(username, "{noop}123456",
                          true, true, true, false,
                          AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));

处理密码加密解密

密码加密解密是使用了下面这个类

    org.springframework.security.crypto.password.PasswordEncoder

在自己摸索中也经常提示 这个类或则什么id为null;

配置只需要提供一个实例即可,会自动使用该实例

    @Configuration
    public class BrowserSecurityConfig extends WebSecurityConfigurerAdapter {
        @Bean
        public PasswordEncoder passwordEncoder() {
            return new BCryptPasswordEncoder();
        }

在UserDetailsService中需要模拟存入数据库中的密码就是加密后的字符串

    @Component
    public class MyUserDetailsService implements UserDetailsService {
        Logger logger = LoggerFactory.getLogger(getClass());
    
        @Autowired
        private PasswordEncoder passwordEncoder;
    
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
            logger.info("登录用户名:{}", username);
            String password = passwordEncoder.encode("123456");
            logger.info("数据库密码{}", password);
            User admin = new User(username,
    //                              "{noop}123456",
                                  password,
                                  true, true, true, true,
                                  AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
        }
    }

其实框架会把提交的密码使用我们定义的passwordEncode加密后调用 org.springframework.security.crypto.password.PasswordEncoder#matches方法,
与 返回的User中的密码进行比对。配对正常就验证通过;

唯一感觉奇怪的是{noop}123456方式的密码居然可以不用写了;
尝试过{bcrypt}123456还是不行

尝试两次登录,发下每次加密的都不一样,但是还能对上结果。感觉很强大;
介绍说:对于同一个串加密多次产生的不一样,就不会存在暴利破解一个串,其他串都失效的情况.

但是本人还是没有想明白。总感觉哪里没有想明白一样,不同的串都能反推出来,有啥安全的?

    2018-08-03 13:45:58.614  INFO 18300 --- [nio-8080-exec-3] c.m.i.s.s.MyUserDetailsService           : 登录用户名:admin
    2018-08-03 13:45:58.708  INFO 18300 --- [nio-8080-exec-3] c.m.i.s.s.MyUserDetailsService           : 数据库密码$2a$10$TaQerjh.VaTRfSLxUozH/eaxgZAcM1H7b0NHEj3peL8Ar8cGfY/R.
    2018-08-03 13:47:08.035  INFO 18300 --- [nio-8080-exec-7] c.m.i.s.s.MyUserDetailsService           : 登录用户名:admin
    2018-08-03 13:47:08.128  INFO 18300 --- [nio-8080-exec-7] c.m.i.s.s.MyUserDetailsService           : 数据库密码$2a$10$jR3gKmOp7LifbXPPHie.JuOW1FmqklzsdZm1spK/r19MkXjpPOa4a

总结

  • 处理用户信息获取逻辑 使用 UserDetailsService
  • 处理用户校验逻辑 使用 UserDetails
  • 处理密码加密解密 使用 PasswordEncoder
阅读全文