无痕刷新token-无需提供刷新token接口方式

一,提供一个login接口获取token:

     1,通过jwt的方式生成token:

private static SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS512;
//用于进行签名的秘钥
private static String SECRET = "QWEQDADW";

/**
 * 生成加密解密key
 *
 * @return
 */
private static Key deserializeKey() {
    byte[] decodedKey = Base64.getDecoder().decode(SECRET);
    Key key = new SecretKeySpec(decodedKey, TokenUtils.signatureAlgorithm.getJcaName());
    return key;
}

/**
 * 生成token信息.
 *
 * @param claims 数据声明(Claim)其实就是一个Map,比如我们想放入用户名,
 *               可以简单的创建一个Map然后put进去
 * @return
 * @throws Exception
 */
private static String generateToken(Map<String, Object> claims, Boolean setExpiration) throws Exception {
    Key key = deserializeKey();
    //设置过期时间为10分钟
    Date ecpiration = new Date(System.currentTimeMillis() + 600000L);

    JwtBuilder jwtBuilder = Jwts.builder();
    jwtBuilder.setClaims(claims);
    if (setExpiration) {
        jwtBuilder.setExpiration(ecpiration);
    }

    jwtBuilder.signWith(SignatureAlgorithm.HS512, key); //采用什么算法是可以自己选择的,不一定非要采用HS512
    String result = jwtBuilder.compact();
    return result;
}

/**
 * 解析token信息.
 *
 * @param token 要解析的token信息
 * @return
 * @throws Exception
 */
public static Optional<Claims> getClaimsFromToken(String token) {
    Claims claims;
    Key key = deserializeKey();
    try {
        claims = Jwts.parser()
                .setSigningKey(key)
                .parseClaimsJws(token)
                .getBody();
        return Optional.of(claims);
    } catch (Exception e) {
        return Optional.empty();
    }
}

/**
 * 验证token是否过期
 *
 * @param tooken 要解析的token信息
 * @return true 表示过期,false表示不过期,如果没有设置过期时间,则也不认为过期
 * @throws Exception
 */
private static boolean isExpired(String tooken) throws Exception {
    Optional<Claims> claims = getClaimsFromToken(tooken);
    if (claims.isPresent()) {
        Date expiration = claims.get().getExpiration();
        return expiration.before(new Date());
    }
    return true;
}

/**
 * 获取tooken中的参数值
 *
 * @return
 * @throws Exception
 */
private static Map<String, Object> extractInfo(String token) throws Exception {
    Optional<Claims> claims = getClaimsFromToken(token);
    if (claims.isPresent()) {
        Map<String, Object> info = new HashMap<String, Object>();
        Set<String> keySet = claims.get().keySet();
        //通过迭代,提取token中的参数信息
        Iterator<String> iterator = keySet.iterator();
        while (iterator.hasNext()) {
            String key = iterator.next();
            Object value = claims.get().get(key);
            info.put(key, value);
        }
        return info;
    }
    return null;
}

public static String getToken(Map map){

    try {
        map.put("CREATE_TIME",System.currentTimeMillis());
        return generateToken(map,false);
    } catch (Exception e) {
        e.printStackTrace();
    }
    return null;
}

     2,在获取token时,将有效的token 以key-value的形式key:token,value:用户信息 放入redis,此token 过期时间稍微长点1-2h都行(根据实际业务设置token有效期)。

二,网关验证:

     1,将token放在请求头或者cookie中。

     2,拦截获取token,获取到的token,去redis验证是否存在此token,存在,使用用户信息验证此用户是否是有效合法的。

    3,提取token中用户信息。

    4,验证是否需要刷新token,这里利用用户id在本地缓存获取上一次调用的时间与当前时间对比。

    5,利用当前时间比较上一次调用的时间差大于等于两分钟(此处允许两分钟,是考虑到当前的这个请求没有正常返回token的情况,这样刷新前的token 才继续有效使用,不然会要求用户进行登陆(无痕就不存在了))就进行刷新token。

   6,token刷新(生成新的token)后紧接着会将上个token从redis中移除,并将这个新token ,set到redis中,反之不会生成新token,也不会将token从redis中删除。

   7,将有效的token 通过response.addheaders("token",token)的方式返回给前端,前端每次从response header中获取有效的token,进行下一次调用的有效token来源(通过此方式传递token 的好处在于不需要暴露刷新接口,可以避免被恶意调用刷新token进行攻击)。

codeing:

@Slf4j
@Component
public class LoginInterceptor extends HandlerInterceptorAdapter {

    @Autowired
    private UserService UserService;
    @Autowired
    private RedisCache redisCache;

    private Map<Long, User> cacheMap = new HashMap();
    private Map<Long ,Long> cacheExpirationTime = new HashMap<>();

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        String basePath = request.getContextPath();
        String path = request.getRequestURI();
        if (!doLoginInterceptor(path, basePath)) {//是否进行登陆拦截
            return true;
        }
        String token = request.getHeader(Constant.AUTHORIZATION);
        log.info("==========登陆token信息[{}]", JSON.toJSONString(token));
        if (StringUtils.isNotBlank(token)) {

            Object userId = redisCache.checkToken(token);
            if (Objects.isNull(userId)) {
                log.info("==========当前token 缓存中已经过期[{}]", token);
                response(response, ResultUtils.error(new AuthServiceException(ResponseCommon.USER_LOGIN)));
                return false;
            } else if (!verifyLoginInfo(userId)) {
                log.info("==========当前token信息存在异常[{}]", token);
                response(response, ResultUtils.error(new AuthServiceException(ResponseCommon.USER_LOGIN_EXCEPTION)));
                return false;
            }
            addResponseHeaders(response, token, userId);
        } else {
            log.info("==========请求信息没有token");
            response(response, ResultUtils.error(new AuthServiceException(ResponseCommon.USER_LOGIN)));
            return false;
        }

        log.info("用户已登录,userName:" + TokenUtils.extractInfo(token));

        return true;
    }


    private void addResponseHeaders(HttpServletResponse response, String token, Object userId) {
        String newToken = refreshToken(token, userId);
        response.setHeader("Access-Control-Expose-Headers", Constant.AUTHORIZATION);
        response.setHeader(Constant.AUTHORIZATION, newToken);
    }

    /**
     * 是否进行登陆过滤
     *
     * @param path
     * @param basePath
     * @return
     */
    private boolean doLoginInterceptor(String path, String basePath) {
        path = path.substring(basePath.length());
        log.info("request url: {}", path);
        List<String> notLoginPaths = Arrays.asList(CommonUrlConstant.USER_LOGIN"); 
        if (notLoginPaths.contains(path)) {
            return false;
        }
        return true;
    }


    private void response(HttpServletResponse response, ApiResultModel message) {

        response.setCharacterEncoding(StandardCharsets.UTF_8.toString());
        response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
        PrintWriter printWriter = null;
        try {
            printWriter = response.getWriter();
            printWriter.println(JSON.toJSONString(message));
            printWriter.flush();
        } catch (IOException e) {
            log.error("拦截器响应异常,respJson:" + message, e);
        } finally {
            if (printWriter != null) {
                printWriter.close();

            }
        }
    }

    /**
     * 返回false 不通过
     *
     * @param userId
     * @return
     */
    private Boolean verifyLoginInfo(Object userId) {

        try {
            if (Objects.isNull(userId)) {
                return false;
            }
            if (cacheMap.size() > 500) {
                cacheMap.clear();
            }
            User userInfo = cacheMap.get(Long.parseLong(userId.toString()));
            if (Objects.isNull(userInfo)) {
                LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper();
                queryWrapper.eq(User::getId, userId);
                userInfo = userService.getOne(queryWrapper);
                if (Objects.nonNull(userInfo)){
                    cacheMap.put(userInfo.getId(), userInfo);
                }
            }
            log.info("=============校验当前用户登陆信息[{}]查询结果[{}] 校验结果[{}]", userId, JSON.toJSONString(userInfo), Objects.nonNull(userInfo));
            addInfo(userInfo);
            return Objects.nonNull(userInfo);
        } catch (Exception e) {
            log.info("=============校验当前用户登陆信息[{}] 校验异常[{}]", userId, e);
        }
        return false;
    }

    /**
     * 添加用户信息     *
     * @param user
     */
    private static void addInfo(User user) {
        if (Objects.nonNull(user)) {
            LoginReq loginReq = new LoginReq();
            BeanCopier beanCopier = BeanCopier.create(User.class, LoginReq.class, Boolean.FALSE);
            beanCopier.copy(user, loginReq, null);
            loginReq.setUserId(user.getId());
            HolderUtil.add(loginReq);
        }
    }

    /**
     * 刷新token
     *
     * @param token
     * @param userIdObject
     * @return
     */
    private String refreshToken(String token, Object userIdObject) {

        if (!isRefreshToken(token)) {
            log.info("使用旧token[{}]",token);
            return token;
        }
        if (oldTokenHandler(token)) {
            User user = cacheMap.get(userIdObject);
            if (Objects.nonNull(user)) {
                String newToken = getToken(user);
                redisCache.setToken(newToken, user.getId());
                log.info("使用新token[{}]",newToken);
                return newToken;
            }
        }
        log.info("使用旧token[{}]",token);
        return token;
    }

    /**
     * 处理旧token
     *
     * @param oleToken
     * @return
     */
    private boolean oldTokenHandler(String oleToken) {
        return redisCache.remove(oleToken);

    }

    /**
     * 获取token
     *
     * @param user
     * @return
     */
    private String getToken(User user) {
        return TokenUtils.getToken(user.getName(), user.getId().toString(), user.getPassword());
    }

    /**
     * 提取用户信息
     *
     * @param token
     * @return
     */
    private static User extractTokenInfo(String token) {
        try {
            Map info = TokenUtils.extractInfo(token);
            return ConvertUtil.objectConvertClass(info, User.class);
        } catch (Exception e) {
            log.info("extract token info fail[{}] token info[{}]", e, token);
        }
        return null;
    }

    /**
     * 相差时间(分钟)
     *
     * @param user
     * @return
     */
    private  Long tokenRemainingTime(User user) {
        Long cacheTime = cacheExpirationTime.get(user.getId());
        if (Objects.isNull(cacheTime)){
            cacheTime = user.getCreateTime();
        }
        Long currentTime = System.currentTimeMillis();
        Long differenceTime = (currentTime / DateUtil.ONE_MINUTES_MILLS) - (cacheTime / DateUtil.ONE_MINUTES_MILLS);
        cacheExpirationTime.put(user.getId(),currentTime);
        return differenceTime;
    }

    /**
     * 是否刷新token(token 过期时间小于2分钟刷新)
     * @param token
     * @return
     */
    private  Boolean isRefreshToken(String token) {

        User user = extractTokenInfo(token);
        if (Objects.isNull(user)) {
            return false;
        }
        Long differenceTime = tokenRemainingTime(user);
        log.info("剩余时间[{}]",differenceTime);
        if (differenceTime >= 2) {
            return true;
        }
        return false;
    }

}
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值