• 渐进式 shiro - shiro + jwt+salt (二)


    本文简述:密码加盐,比较策略修改.关于密码加盐详解,见此文章

    shiro 完整流程以及集成:

    1. 用户访问注册接口 /regester, 用户输入登录账号和密码,将密码加盐后存入数据库
    2. 用户访问登录接口 /login, 用户输入登录账号和密码被封装成 UsernamePasswordToken 对象,然后调用 subject.login() 方法
    3. 在 shiroConfig 设置filterChainDefinitionMap.put("/login", "anon");, 也就意味着将 /login登录请求交给 shiro提供的 anno 过滤器处理。
    4. 因此,登录服务中调用 subject.login() 方法,shiro 立即进入用户认证过程,此认证过程就是检验用户账号和密码是否正确,交给了 自定义Realm 来处理。
    5. 自定义Realm中,方法 doGetAuthenticationInfo(AuthenticationToken authenticationToken) 接收一个参数 authenticationToken
      此时注意第 2 步,登录账号和密码被封装成了 UsernamePasswordToken 对象,而 UsernamePasswordTokenAuthenticationToken 接口的实现类,因此这里的 authenticationToken内部就储存这用户输入的账号和密码.
    <dependency>
        <groupId>org.apache.shirogroupId>
        <artifactId>shiro-spring-boot-web-starterartifactId>
        <version>1.10.0version>
    dependency>
    <dependency>
        <groupId>org.apache.commonsgroupId>
        <artifactId>commons-lang3artifactId>
        <version>3.12.0version>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    流程例子

    shiro 运行流程,使用此小例子简单说明。(由于此处做演示,为了代码可以运行,因此没有 subjet.login() 这一行代码)

    @SpringBootTest
    class UserControllerTest {
        @Test
        public void testShiroEncryptPassword(){
            // 第一步 (登录封装)
            String username = "ifredom";
            String password = "123456";
            String salt = RandomStringUtils.randomAlphanumeric(20);
            UserEntity userEntity = new UserEntity();
            userEntity.setUsername(username);
            userEntity.setPassword(password);
            AuthenticationToken token = new UsernamePasswordToken(username, password);
    
            // 第二步 (认证流程)
            SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
                    userEntity, //用户名
                    password, //密码
                    ByteSource.Util.bytes(salt),// salt
                    "anyRealmName"  //realm name
            );
            
            // 第三步 (返回,shiro内部比较)
            // 比较
            SimpleCredentialsMatcher matcher = new SimpleCredentialsMatcher();
            boolean match = matcher.doCredentialsMatch(token, authenticationInfo);
            System.out.println(match);
    
            if (match) {
                System.out.println("密码相同");
            }else{
                System.out.println("密码不同");
            }
    
        }
    }
    
    • 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

    controller 注册与登录

    注册用户时,为了保护用户的密码,使用 MD5 算法对其进行一层加密,并将加密后的密码存入数据库。

    public class UserVo extends UserEntity {}
    
    • 1

    将密码进行加盐(密)处理:

    • 使用 shiro 提供的类 SimpleHash ,对密码执行 MD5 hash 算法,加密后存入数据库
    • 使用第三方工具包 commons-lang3提供的方法生成一个随机字符串,取名为 salt 盐,并存入数据库
    @RestController
    public class UserController {
    
        @Autowired
        private UserService userService;
    
        /**
         * 注册新用户
         *
         */
        @PostMapping("/register")
        public AjaxResult registerUser(@RequestBody UserVo userVo) {
    
            UserEntity user = new UserEntity();
    
            String salt = RandomStringUtils.randomAlphanumeric(20);
    
            //加密
            SimpleHash simpleHash = new SimpleHash("md5",userVo.getPassword(),salt,1);
            String hashPassword = simpleHash.toString();
    
            user.setUsername(userVo.getUsername());
            user.setPassword(hashPassword);
            user.setSalt(salt);
    
            userService.save(user);
            return AjaxResult.success();
        }
    
        /**
         * 登录
         *
         */
        @PostMapping("/login")
        public String loginUser(@Validated @RequestBody UserVo userVo, BindingResult bindingResult) {
            Subject subject = SecurityUtils.getSubject();
    
            UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(userVo.getUsername(), userVo.getPassword());
            usernamePasswordToken.setRememberMe(true);
    
            try {
                subject.login(usernamePasswordToken);
                return "登录成功";
            } catch (AuthenticationException ae) {
                return "登录失败: " + ae.getMessage();
            }
    
            return "登录成功";
        }
    }
    
    • 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
    @Configuration
    public class ShiroConfig {
        /**
         * `SecurityManager`:安全管理器
         *
         */
        @Bean
        public DefaultWebSecurityManager getDefaultWebSecurityManager() {
            DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
            securityManager.setRealm(userRealm());
            return securityManager;
        }
    
        /**
         * `shiroFilter`:过滤器
         *
         */
        @Bean
        public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager,ShiroFilterChainDefinition shiroFilterChainDefinition) {
            ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
            // Shiro的核心安全接口,这个属性是必须的
            shiroFilterFactoryBean.setSecurityManager(securityManager);
    
            // 定义过滤链
            Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
            // 对静态资源设置匿名访问
            filterChainDefinitionMap.put("/index.html", "anon");
            filterChainDefinitionMap.put("/favicon.ico**", "anon");
            filterChainDefinitionMap.put("/static/**","anon");
    
            // 登录,不需要拦截的访问
            filterChainDefinitionMap.put("/login", "anon");
            // 错误页面无需认证
            filterChainDefinitionMap.put("/error","anon");
            // 其余资源都需要认证
            filterChainDefinitionMap.put("/**","authc");
    
            shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
            return shiroFilterFactoryBean;
        }
        /**
         * `Realm`:realm 认证流程
         *
         */
        @Bean
        public UserRealm userRealm() {
            UserRealm userRealm = new UserRealm();
    
            /**
            * 设置密码匹配策略
            * 因为在注册时,密码使用MD5算法执行了 1 次hash化
            *
            */
            HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
            hashedCredentialsMatcher.setHashAlgorithmName("md5");
            userRealm.setCredentialsMatcher(hashedCredentialsMatcher);
    
            return userRealm;
        }
    }
    
    • 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
    public class UserRealm extends AuthorizingRealm {
    
        @Autowired
        private UserService userService;
    
        /**
         * 授权
         *
         */
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
            return null;
        }
    
        /**
         * 验证:用户登录
         *
         */
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
            UsernamePasswordToken accessToken = (UsernamePasswordToken) authenticationToken;
    
            // 查询用户
            UserEntity user = Optional.ofNullable(userService.getOne(new LambdaQueryWrapper<UserEntity>().eq(UserEntity::getUsername, accessToken.getUsername())))
                    .orElseThrow(() -> new UnknownAccountException("账号或密码不正确"));
    
            //  验证密码
            SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
                    user,
                    user.getPassword(),
                    ByteSource.Util.bytes(user.getSalt()),
                    getName()
            );
            return authenticationInfo;
        }
    }
    
    • 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

    到此,注册用户并对密码加盐就完成了,并且可以随时替换算法,只需要

    1. 在注册时,修改对密码执行的算法
    2. 在 shiroConfig 的 HashedCredentialsMatcher 中,匹配策略保持与注册时的算法一致即可
  • 相关阅读:
    两节点DC-OPF
    Eureka:微服务中的服务注册与发现机制
    logstash 采集的文件mv后
    lc[栈与队列]---225.用队列实现栈
    大数据培训课程MapTask工作机制
    解决iView中InpuNuber限定小数位时,输入光标经常后移的问题
    Liunx-05磁盘管理
    常用的8位单片机+2.4g遥控芯片的“化学”反应
    每天一个设计模式之观察者模式+发布订阅模式(Observer Pattern)
    【JAVASE】面向对象的三大特征
  • 原文地址:https://blog.csdn.net/win7583362/article/details/127899280