解决Shiro jwt并发刷新token问题

        使用shiro jwt做应用系统的权限校验,网上通用的方式是这样的。

在用户登录时候会生成两份token,一份AccessToken用于返回给前端,前端带上这个令牌去请求后台接口,通常过期时间较短5分钟左右,一份RefreshToken放在Redis中,两个Token的加密值都是当前时间戳,当用户的AccessToken过期时,就去从Redis中通过用户名取得Redis中的RefreshToken,比较AccessToken和RefreshToken中的时间戳是否一致,如果一致就重新生成一组AccessToken和RefreshToken,他们值都是当前时间戳。然后返回给前端。用户继续带着这个新AccessToken请求接口,原则上如果用户持续点击,就可以一致无限的刷新Token,但是这种方式如果页面都是单请求,自然没有问题,但是页面不可避免的都会有并发的请求。如果有并发请求过来,只有第一个请求可以正常返回,其他的后续所有请求都会失败,因为第一个请求已经刷新的Redis中的时间戳,返回给了前端的新的AccessToken,后续的并发请求自然带着老AccessToken就会全部报错。

        这种情况怎么解决呢?

        1. 保证前端请求的顺序执行,这种方式比较消耗前端性能接口请求较慢

        2. 在后端刷新RefreshToken写入Redis时加入Redis写入锁,只有最先最快拿到锁的线程允许修改Redis中的值,锁定时间设置为30s,30s后解锁,写入数据。

        3.缓存旧的AccessToken,每次需要刷新Token的时候都把老Token存储一份,可以放在Redis中,或者一个全局变量中,有效时间30s。也就是说30s内带着老Token请求依然有效

        这里主要详细描述下第三种方式如何实现。

//全局变量用于缓存旧的Token
 Map tempToken = new HashMap<>();
  /**
     * shiro验证成功调用
     * @param token
     * @param subject
     * @param request
     * @param response
     * @return
     * @throws Exception
     */
    @Override
    protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request, ServletResponse response) throws Exception {
        String jwttoken= (String) token.getPrincipal();
        if (jwttoken!=null){
            try{
                if(TokenUtil.verify(jwttoken)){
                    //判断Redis是否存在所对应的RefreshToken
                    String account = TokenUtil.getAccount(jwttoken);
                    Long currentTime=TokenUtil.getCurrentTime(jwttoken);
                    if (RedisUtil.hasKey(account)) {
                        Long currentTimeMillisRedis = (Long) RedisUtil.get(account);
                        if (currentTimeMillisRedis.equals(currentTime)) {
                            return true;
                        }
                    }
                }
                return false;
            }catch (Exception e){
                if (e instanceof TokenExpiredException){
                    System.out.println("AccessToken过期了");
                    if(tempToken.containsKey(jwttoken)){
                        // 判断缓存中是否有token,有的话判断时间是否小于30s,小于30 通过,大于30 清空map
                        Long currentTimeMillis =System.currentTimeMillis();
                        long r = (currentTimeMillis - tempToken.get(jwttoken))/1000;
                        if(r <= 30 ){
                            System.out.println("小于30 所以通过");
                            return true;
                        }else{
                            System.out.println("大于30 所以通过");
                            tempToken.clear();
                            return false;
                        }
                    }
                    if (refreshToken(request, response)) {
                        return true;
                    }else {
                        return false;
                    }
                }
            }
        }
        return true;
    }

    /**
     * 刷新AccessToken,进行判断RefreshToken是否过期,未过期就返回新的AccessToken且继续正常访问
     * @param request
     * @param response
     * @return
     */
    private boolean refreshToken(ServletRequest request, ServletResponse response) {
        String token = ((HttpServletRequest)request).getHeader("X-Auth-Token");
        String account = TokenUtil.getAccount(token);
        Long currentTime=TokenUtil.getCurrentTime(token);
        // 判断Redis中RefreshToken是否存在
        if (RedisUtil.hasKey(account)) {
            // Redis中RefreshToken还存在,获取RefreshToken的时间戳
            Long currentTimeMillisRedis = (Long) RedisUtil.get(account);
            // 获取当前AccessToken中的时间戳,与RefreshToken的时间戳对比,如果当前时间戳一致,进行AccessToken刷新
            if (currentTimeMillisRedis.equals(currentTime)) {
                // 获取当前最新时间戳
                Long currentTimeMillis =System.currentTimeMillis();
                tempToken.put(token,currentTimeMillis);
                RedisUtil.set(account, currentTimeMillis,
                        CommonData.REFRESH_EXPIRE_TIME);
                // 刷新AccessToken,设置时间戳为当前最新时间戳
                token = TokenUtil.sign(account, currentTimeMillis);
                HttpServletResponse httpServletResponse = (HttpServletResponse) response;
                httpServletResponse.setHeader("Authorization", token);
                httpServletResponse.setHeader("Access-Control-Expose-Headers", "Authorization");
                return true;
            }
        }
        return false;
    }

你可能感兴趣的:(java,java,jwt,并发刷新Token,shiro)