• Shiro实现多realm方案


    大家好,我是程序员田同学。

    公司开始了新项目,新项目的认证采用的是Shiro实现。由于涉及到多端登录用户,而且多端用户还是来自不同的表。

    这就涉及到了Shiro的多realm,今天的demo主要是介绍Shiro的多realm实现方案,文中包含所有的代码,需要的朋友可以无缝copy。

    image-20220627101300075

    前后端分离的背景下,在认证的实现中主要是两方面的内容,一个是用户登录获取到token,二是从请求头中拿到token并检验token的有效性和设置缓存。

    1、用户登录获取token

    登录和以往单realm实现逻辑一样,使用用户和密码生成token返回给前端,前端每次请求接口的时候携带token。

      @ApiOperation(value="登录", notes="登录")
        public Result<JSONObject> wxappLogin(String username,String password){
         Result<JSONObject> result = new Result<JSONObject>();
            JSONObject obj = new JSONObject();
    	// 生成token
    	String password="0";
    	String token = JwtUtil.sign(username, password);
            obj.put("token", token);
            result.setResult(obj);
            result.success("登录成功");
    
            return result;
            }	
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    生成token的工具类

    /**
     * 生成签名,5min后过期
     *
     * @param username 用户名
     * @param secret   用户的密码
     * @return 加密的token
     */
    public static String sign(String username, String secret) {
       Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME);
       Algorithm algorithm = Algorithm.HMAC256(secret);
       // 附带username信息
       return JWT.create().withClaim("username", username).withExpiresAt(date).sign(algorithm);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    以上就实现了简单的登录逻辑,和Shiro的单realm设置和SpringSecurity的登录逻辑都没有什么区别。

    2、鉴权登录拦截器(验证token有效性)

    使用Shiro登录拦截器的只需要继承Shiro的 BasicHttpAuthenticationFilter 类 重写 isAccessAllowed()方法,在该方法中我们从ServletRequest中获取到token和login_type。

    需要特别指出的是,由于是多realm,我们在请求头中加入一个login_type来区分不同的登录类型。

    通过token和login_type我们生成一个JwtToken对象提交给getSubject。

    JwtFilter过滤器

    @Slf4j
    public class JwtFilter extends BasicHttpAuthenticationFilter {
    
        /**
         * 默认开启跨域设置(使用单体)
         */
        private boolean allowOrigin = true;
    
        public JwtFilter(){}
        public JwtFilter(boolean allowOrigin){
            this.allowOrigin = allowOrigin;
        }
    
        /**
         * 执行登录认证
         *
         * @param request
         * @param response
         * @param mappedValue
         * @return
         */
        @Override
        protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
            try {
                executeLogin(request, response);
                return true;
            } catch (Exception e) {
                JwtUtil.responseError(response,401,CommonConstant.TOKEN_IS_INVALID_MSG);
                return false;
                //throw new AuthenticationException("Token失效,请重新登录", e);
            }
        }
    
        /**
         *
         */
        @Override
        protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
            HttpServletRequest httpServletRequest = (HttpServletRequest) request;
            String token = httpServletRequest.getHeader(CommonConstant.X_ACCESS_TOKEN);
            String loginType = httpServletRequest.getHeader(CommonConstant.LOGIN_TYPE);
            // update-begin--Author:lvdandan Date:20210105 for:JT-355 OA聊天添加token验证,获取token参数
            if (oConvertUtils.isEmpty(token)) {
                token = httpServletRequest.getParameter("token");
            }
            // update-end--Author:lvdandan Date:20210105 for:JT-355 OA聊天添加token验证,获取token参数
    
            JwtToken jwtToken = new JwtToken(token,loginType);
            // 提交给realm进行登入,如果错误他会抛出异常并被捕获
            getSubject(request, response).login(jwtToken);
            // 如果没有抛出异常则代表登入成功,返回true
            return true;
        }
      
    }
    
    • 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

    JwtToken类

    public class JwtToken implements AuthenticationToken {
       
       private static final long serialVersionUID = 1L;
       private String token;
    
        private String loginType;
    //    public JwtToken(String token) {
    //        this.token = token;
    //    }
        public JwtToken(String token,String loginType) {
            this.token = token;
            this.loginType=loginType;
        }
        public String getToken() {
            return token;
        }
    
        public void setToken(String token) {
            this.token = token;
        }
    
        public String getLoginType() {
            return loginType;
        }
    
        public void setLoginType(String loginType) {
            this.loginType = loginType;
        }
    
        @Override
        public Object getPrincipal() {
            return token;
        }
     
        @Override
        public Object getCredentials() {
            return token;
        }
    }
    
    • 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

    再往下的逻辑肯定会先根据我们的login_type来走不同的realm了,然后在各自的realm中去检查token的有效性了,那Shiro怎么知道我们的Realm都是哪些呢?

    接下来就该引出使用Shiro的核心配置文件了——ShiroConfig.java类

    shiro的配置文件中会注入名字为securityManager的Bean。

    在该bean中首先注入ModularRealmAuthenticator,ModularRealmAuthenticator会根据配置的AuthenticationStrategy(身份验证策略)进行多Realm认证过程。

    由于是多realm我们需要重写ModularRealmAuthenticator类,ModularRealmAuthenticator类中用于判断逻辑走不同的realm,接着注入我们的两个realm,分别是myRealm和clientShiroRealm。

    重新注入 ModularRealm类

     @Bean
        public ModularRealm ModularRealm(){
            //自己重写的ModularRealmAuthenticator
            ModularRealm modularRealm = new ModularRealm();
    //        modularRealmAuthenticator.setAuthenticationStrategy(new AtLeastOneSuccessfulStrategy());//这里为默认策略:如果有一个或多个Realm验证成功,所有的尝试都被认为是成功的,如果没有一个验证成功,则该次尝试失败
            return modularRealm;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    securityManager-bean。

     @Bean("securityManager")
        public DefaultWebSecurityManager securityManager(ShiroRealm myRealm,
        ClientShiroRealm clientShiroRealm,ModularRealm modularRealm) {
            DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
    //        securityManager.setRealm(myRealm);
    
            securityManager.setAuthenticator(modularRealm);
            List<Realm> realms = new ArrayList<>();
            //添加多个Realm
            realms.add(myRealm);
            realms.add(clientShiroRealm);
            securityManager.setRealms(realms);
    
            /*
             * 关闭shiro自带的session,详情见文档
             * http://shiro.apache.org/session-management.html#SessionManagement-
             * StatelessApplications%28Sessionless%29
             */
            DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
            DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
            defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
            subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
            securityManager.setSubjectDAO(subjectDAO);
            //自定义缓存实现,使用redis
            securityManager.setCacheManager(redisCacheManager());
            return securityManager;
        }
    
    • 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

    ModularRealm实现类

    public class ModularRealm  extends ModularRealmAuthenticator {
    
        @Override
        protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
            assertRealmsConfigured();
            Collection<Realm> realms = getRealms();
            // 登录类型对应的所有Realm
            HashMap<String, Realm> realmHashMap = new HashMap<>(realms.size());
    
            for (Realm realm : realms) {
                // 这里使用的realm中定义的Name属性来进行区分,注意realm中要加上
                realmHashMap.put(realm.getName(), realm);
            }
    
            JwtToken token = (JwtToken) authenticationToken;
    
            if (StrUtil.isEmpty(token.getLoginType())){
                return  doSingleRealmAuthentication(realmHashMap.get(LoginType.DEFAULT.getType()),token);
            } else {
                return  doSingleRealmAuthentication(realmHashMap.get(token.getLoginType()),token);
            }
    
    //        return super.doAuthenticate(authenticationToken);
        }
    }
    
    • 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

    然后会根据不同的login_type到不同的realm,下面为我的Shiro认证realm。

    myrealm类.

    @Component
    @Slf4j
    public class ShiroRealm extends AuthorizingRealm {
       @Lazy
        @Resource
        private CommonAPI commonApi;
    
        @Lazy
        @Resource
        private RedisUtil redisUtil;
    
        @Override
        public String getName() {
            return LoginType.DEFAULT.getType();
        }
        /**
         * 必须重写此方法,不然Shiro会报错
         */
        @Override
        public boolean supports(AuthenticationToken token) {
    //        return token instanceof JwtToken;
            if (token instanceof JwtToken){
                return StrUtil.isEmpty(((JwtToken) token).getLoginType()) || LoginType.CLIENT.getType().equals(((JwtToken) token).getLoginType());
            } else {
                return false;
            }
        }
    
        /**
         * 权限信息认证(包括角色以及权限)是用户访问controller的时候才进行验证(redis存储的此处权限信息)
         * 触发检测用户权限时才会调用此方法,例如checkRole,checkPermission
         *
         * @param principals 身份信息
         * @return AuthorizationInfo 权限信息
         */
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
            log.debug("===============Shiro权限认证开始============ [ roles、permissions]==========");
            String username = null;
            if (principals != null) {
                LoginUser sysUser = (LoginUser) principals.getPrimaryPrincipal();
                username = sysUser.getUsername();
            }
            SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
    
            // 设置用户拥有的角色集合,比如“admin,test”
            Set<String> roleSet = commonApi.queryUserRoles(username);
            System.out.println(roleSet.toString());
            info.setRoles(roleSet);
    
            // 设置用户拥有的权限集合,比如“sys:role:add,sys:user:add”
            Set<String> permissionSet = commonApi.queryUserAuths(username);
            info.addStringPermissions(permissionSet);
            System.out.println(permissionSet);
            log.info("===============Shiro权限认证成功==============");
            return info;
        }
    
        /**
         * 用户信息认证是在用户进行登录的时候进行验证(不存redis)
         * 也就是说验证用户输入的账号和密码是否正确,错误抛出异常
         *
         * @param auth 用户登录的账号密码信息
         * @return 返回封装了用户信息的 AuthenticationInfo 实例
         * @throws AuthenticationException
         */
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken auth) throws AuthenticationException {
            log.debug("===============Shiro身份认证开始============doGetAuthenticationInfo==========");
            String token = (String) auth.getCredentials();
            if (token == null) {
                HttpServletRequest req = SpringContextUtils.getHttpServletRequest();
                log.info("————————身份认证失败——————————IP地址:  "+ oConvertUtils.getIpAddrByRequest(req) +",URL:"+req.getRequestURI());
                throw new AuthenticationException("token为空!");
            }
            // 校验token有效性
            LoginUser loginUser = null;
            try {
                loginUser = this.checkUserTokenIsEffect(token);
            } catch (AuthenticationException e) {
                JwtUtil.responseError(SpringContextUtils.getHttpServletResponse(),401,e.getMessage());
                e.printStackTrace();
                return null;
            }
            return new SimpleAuthenticationInfo(loginUser, token, getName());
        }
    
        /**
         * 校验token的有效性
         *
         * @param token
         */
        public LoginUser checkUserTokenIsEffect(String token) throws AuthenticationException {
            // 解密获得username,用于和数据库进行对比
            String username = JwtUtil.getUsername(token);
            if (username == null) {
                throw new AuthenticationException("token非法无效!");
            }
    
            // 查询用户信息
            log.debug("———校验token是否有效————checkUserTokenIsEffect——————— "+ token);
            LoginUser loginUser = TokenUtils.getLoginUser(username,commonApi,redisUtil);
            //LoginUser loginUser = commonApi.getUserByName(username);
            if (loginUser == null) {
                throw new AuthenticationException("用户不存在!");
            }
            // 判断用户状态
            if (loginUser.getStatus() != 1) {
                throw new AuthenticationException("账号已被锁定,请联系管理员!");
            }
            // 校验token是否超时失效 & 或者账号密码是否错误
            if (!jwtTokenRefresh(token, username, loginUser.getPassword())) {
                throw new AuthenticationException(CommonConstant.TOKEN_IS_INVALID_MSG);
            }
            //update-begin-author:taoyan date:20210609 for:校验用户的tenant_id和前端传过来的是否一致
            String userTenantIds = loginUser.getRelTenantIds();
            if(oConvertUtils.isNotEmpty(userTenantIds)){
                String contextTenantId = TenantContext.getTenant();
                String str ="0";
                if(oConvertUtils.isNotEmpty(contextTenantId) && !str.equals(contextTenantId)){
                    //update-begin-author:taoyan date:20211227 for: /issues/I4O14W 用户租户信息变更判断漏洞
                    String[] arr = userTenantIds.split(",");
                    if(!oConvertUtils.isIn(contextTenantId, arr)){
                        throw new AuthenticationException("用户租户信息变更,请重新登陆!");
                    }
                    //update-end-author:taoyan date:20211227 for: /issues/I4O14W 用户租户信息变更判断漏洞
                }
            }
            //update-end-author:taoyan date:20210609 for:校验用户的tenant_id和前端传过来的是否一致
            return loginUser;
        }
    
        /**
         * JWTToken刷新生命周期 (实现: 用户在线操作不掉线功能)
         * 1、登录成功后将用户的JWT生成的Token作为k、v存储到cache缓存里面(这时候k、v值一样),缓存有效期设置为Jwt有效时间的2倍
         * 2、当该用户再次请求时,通过JWTFilter层层校验之后会进入到doGetAuthenticationInfo进行身份验证
         * 3、当该用户这次请求jwt生成的token值已经超时,但该token对应cache中的k还是存在,则表示该用户一直在操作只是JWT的token失效了,程序会给token对应的k映射的v值重新生成JWTToken并覆盖v值,该缓存生命周期重新计算
         * 4、当该用户这次请求jwt在生成的token值已经超时,并在cache中不存在对应的k,则表示该用户账户空闲超时,返回用户信息已失效,请重新登录。
         * 注意: 前端请求Header中设置Authorization保持不变,校验有效性以缓存中的token为准。
         *       用户过期时间 = Jwt有效时间 * 2。
         *
         * @param userName
         * @param passWord
         * @return
         */
        public boolean jwtTokenRefresh(String token, String userName, String passWord) {
            String cacheToken = String.valueOf(redisUtil.get(CommonConstant.PREFIX_USER_TOKEN + token));
            if (oConvertUtils.isNotEmpty(cacheToken)) {
                // 校验token有效性
                if (!JwtUtil.verify(cacheToken, userName, passWord)) {
                    //生成token
                    String newAuthorization = JwtUtil.sign(userName, passWord);
                    // 设置超时时间
                    redisUtil.set(CommonConstant.PREFIX_USER_TOKEN + token, newAuthorization);
                    redisUtil.expire(CommonConstant.PREFIX_USER_TOKEN + token, JwtUtil.EXPIRE_TIME *2 / 1000);
                    log.debug("——————————用户在线操作,更新token保证不掉线—————————jwtTokenRefresh——————— "+ token);
                }
                //update-begin--Author:scott  Date:20191005  for:解决每次请求,都重写redis中 token缓存问题
    //       else {
    //          // 设置超时时间
    //          redisUtil.set(CommonConstant.PREFIX_USER_TOKEN + token, cacheToken);
    //          redisUtil.expire(CommonConstant.PREFIX_USER_TOKEN + token, JwtUtil.EXPIRE_TIME / 1000);
    //       }
                //update-end--Author:scott  Date:20191005   for:解决每次请求,都重写redis中 token缓存问题
                return true;
            }
    
            //redis中不存在此TOEKN,说明token非法返回false
            return false;
        }
    
        /**
         * 清除当前用户的权限认证缓存
         *
         * @param principals 权限信息
         */
        @Override
        public void clearCache(PrincipalCollection principals) {
            super.clearCache(principals);
        }
    
    }
    
    • 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
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182

    ClientShiroRealm类.

    @Component
    @Slf4j
    public class ClientShiroRealm extends AuthorizingRealm {
       @Lazy
        @Resource
        private ClientAPI clientAPI;
    
        @Lazy
        @Resource
        private RedisUtil redisUtil;
        @Override
        public String getName() {
            return LoginType.CLIENT.getType();
        }
        /**
         * 必须重写此方法,不然Shiro会报错
         */
        @Override
        public boolean supports(AuthenticationToken token) {
    //        return token instanceof JwtToken;
            if (token instanceof JwtToken){
                return  LoginType.CLIENT.getType().equals(((JwtToken) token).getLoginType());
            } else {
                return false;
            }
        }
    
        /**
         * 权限信息认证(包括角色以及权限)是用户访问controller的时候才进行验证(redis存储的此处权限信息)
         * 触发检测用户权限时才会调用此方法,例如checkRole,checkPermission
         *
         * @param principals 身份信息
         * @return AuthorizationInfo 权限信息
         */
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
            log.debug("===============Shiro权限认证开始============ [ roles、permissions]==========");
            //String username = null;
            //if (principals != null) {
            //    LoginUser sysUser = (LoginUser) principals.getPrimaryPrincipal();
            //    username = sysUser.getUsername();
            //}
            //SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
             设置用户拥有的角色集合,比如“admin,test”
            //Set<String> roleSet = commonApi.queryUserRoles(username);
            //System.out.println(roleSet.toString());
            //info.setRoles(roleSet);
            //
             设置用户拥有的权限集合,比如“sys:role:add,sys:user:add”
            //Set<String> permissionSet = commonApi.queryUserAuths(username);
            //info.addStringPermissions(permissionSet);
            //System.out.println(permissionSet);
            log.info("===============Shiro权限认证成功==============");
            return null;
        }
    
        /**
         * 用户信息认证是在用户进行登录的时候进行验证(不存redis)
         * 也就是说验证用户输入的账号和密码是否正确,错误抛出异常
         *
         * @param auth 用户登录的账号密码信息
         * @return 返回封装了用户信息的 AuthenticationInfo 实例
         * @throws AuthenticationException
         */
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken auth) throws AuthenticationException {
            log.debug("===============Shiro身份认证开始============doGetAuthenticationInfo==========");
            String token = (String) auth.getCredentials();
            if (token == null) {
                HttpServletRequest req = SpringContextUtils.getHttpServletRequest();
                log.info("————————身份认证失败——————————IP地址:  "+ oConvertUtils.getIpAddrByRequest(req) +",URL:"+req.getRequestURI());
                throw new AuthenticationException("token为空!");
            }
            // 校验token有效性
            LoginUser loginUser = null;
            try {
                loginUser = this.checkUserTokenIsEffect(token);
            } catch (AuthenticationException e) {
                JwtUtil.responseError(SpringContextUtils.getHttpServletResponse(),401,e.getMessage());
                e.printStackTrace();
                return null;
            }
            return new SimpleAuthenticationInfo(loginUser, token, getName());
        }
    
        /**
         * 校验token的有效性
         *
         * @param token
         */
        public LoginUser checkUserTokenIsEffect(String token) throws AuthenticationException {
            // 解密获得username,用于和数据库进行对比
            String username = JwtUtil.getUsername(token);
            if (username == null) {
                throw new AuthenticationException("token非法无效!");
            }
    
            // 查询用户信息
            log.debug("———校验token是否有效————checkUserTokenIsEffect——————— "+ token);
            LoginUser loginUser = TokenUtils.getClientLoginUser(username,clientAPI,redisUtil);
            //LoginUser loginUser = commonApi.getUserByName(username);
            if (loginUser == null) {
                throw new AuthenticationException("用户不存在!");
            }
    
            // 校验token是否超时失效 & 或者账号密码是否错误
            if (!jwtTokenRefresh(token, username, loginUser.getPassword())) {
                throw new AuthenticationException(CommonConstant.TOKEN_IS_INVALID_MSG);
            }
            return loginUser;
        }
    
        /**
         * JWTToken刷新生命周期 (实现: 用户在线操作不掉线功能)
         * 1、登录成功后将用户的JWT生成的Token作为k、v存储到cache缓存里面(这时候k、v值一样),缓存有效期设置为Jwt有效时间的2倍
         * 2、当该用户再次请求时,通过JWTFilter层层校验之后会进入到doGetAuthenticationInfo进行身份验证
         * 3、当该用户这次请求jwt生成的token值已经超时,但该token对应cache中的k还是存在,则表示该用户一直在操作只是JWT的token失效了,程序会给token对应的k映射的v值重新生成JWTToken并覆盖v值,该缓存生命周期重新计算
         * 4、当该用户这次请求jwt在生成的token值已经超时,并在cache中不存在对应的k,则表示该用户账户空闲超时,返回用户信息已失效,请重新登录。
         * 注意: 前端请求Header中设置Authorization保持不变,校验有效性以缓存中的token为准。
         *       用户过期时间 = Jwt有效时间 * 2。
         *
         * @param userName
         * @param passWord
         * @return
         */
        public boolean jwtTokenRefresh(String token, String userName, String passWord) {
            String cacheToken = String.valueOf(redisUtil.get(CommonConstant.PREFIX_USER_TOKEN + token));
            if (oConvertUtils.isNotEmpty(cacheToken)) {
                // 校验token有效性
                if (!JwtUtil.verify(cacheToken, userName, passWord)) {
                    //生成token
                    String newAuthorization = JwtUtil.sign(userName, passWord);
                    // 设置超时时间
                    redisUtil.set(CommonConstant.PREFIX_USER_TOKEN + token, newAuthorization);
                    redisUtil.expire(CommonConstant.PREFIX_USER_TOKEN + token, JwtUtil.EXPIRE_TIME *2 / 1000);
                    log.debug("——————————用户在线操作,更新token保证不掉线—————————jwtTokenRefresh——————— "+ token);
                }
    
                return true;
            }
    
            //redis中不存在此TOEKN,说明token非法返回false
            return false;
        }
    
        /**
         * 清除当前用户的权限认证缓存
         *
         * @param principals 权限信息
         */
        @Override
        public void clearCache(PrincipalCollection principals) {
            super.clearCache(principals);
        }
    
    }
    
    • 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
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156

    这两个realm更多的是需要实现我们自身的realm,我把我的全部代码贴上,读者可根据自己的需要进行修改,两个方法大致的作用都是检验token的有效性,只是查询的用户从不同的用户表中查出来的。

    至此,Shiro的多Realm实现方案到这里就正式结束了。

  • 相关阅读:
    版本控制 | 如何有效管理SVN服务器上的多个储存库
    【知识拓展】HTTP、WebSocket 和 RPC:区别与使用场景详解
    ES6学习笔记
    【yolo系列:yolov7改进wise-iou】
    App安全架构之前端安全防护
    JS实时获取录音数据并播放
    论文笔记《3D Gaussian Splatting for Real-Time Radiance Field Rendering》
    每日一问:如何解决kafka的消息丢失与堆积问题
    【日拱一卒行而不辍20220921】自制操作系统
    防火墙ensp实验
  • 原文地址:https://blog.csdn.net/weixin_44643680/article/details/125570556