码迷,mamicode.com
首页 > 其他好文 > 详细

标识jti,然后判断该标识是否存在黑名单列表

时间:2020-11-01 09:27:17      阅读:18      评论:0      收藏:0      [点我收藏+]

标签:java   时间戳   过期   success   The   etl   replace   一句话   fir   

JWT最大的一个优势在于它是无状态的,自身包含了认证鉴权所需要的所有信息,服务器端无需对其存储,从而给服务器减少了存储开销。

但是无状态引出的问题也是可想而知的,它无法作废未过期的JWT。举例说明注销场景下,就传统的cookie/session认证机制,只需要把存在服务器端的session删掉就OK了。但是JWT呢,它是不存在服务器端的啊,好的那我删存在客户端的JWT行了吧。额,社会本就复杂别再欺骗自己了好么,被你在客户端删掉的JWT还是可以通过服务器端认证的。

首先明确一点JWT失效的唯一途径就是等过期,就是说不借助外力的情况下,无法达到某些场景下需要主动使JWT失效的目的。而外力则是在服务器端存储着JWT的状态,在请求资源时添加判断逻辑,这与JWT特性无状态是相互矛盾的存在。但是,你要知道如果你选择走上了JWT这条路,那就没得选了。如果你有好的方式,希望你来打我脸。

以下就JWT在某些场景需要失效的简单方案整理如下:

  1. 白名单方式

认证通过时,把JWT缓存到Redis,注销时,从缓存移除JWT。请求资源添加判断JWT在缓存中是否存在,不存在拒绝访问。这种方式和cookie/session机制中的会话失效删除session基本一致。

  1. 黑名单方式

注销登录时,缓存JWT至Redis,且缓存有效时间设置为JWT的有效期,请求资源时判断是否存在缓存的黑名单中,存在则拒绝访问。

白名单和黑名单的实现逻辑差不多,黑名单不需每次登录都将JWT缓存,仅仅在某些特殊场景下需要缓存JWT,给服务器带来的压力要远远小于白名单的方式。

三. 黑名单方式实现
以下演示在退出登录时通过添加至黑名单的方式实现JWT失效

逻辑很明确,在调用退出登录接口时将JWT缓存到Redis的黑名单中,然后在网关做判定请求头的JWT是否在黑名单内做对应的处理。

  1. 认证中心(youlai-auth)退出登录接口
    登出接口/oauth/logout的主要逻辑把JWT添加至Redis黑名单缓存中,但没必要把整个JWT字符串都存储下来,JWT的载体中有个jti(JWT ID)字段声明为JWT提供了唯一的标识符。JWT解析的结构如下:

既然有这么个字段能作为JWT的唯一标识,从JWT解析出jti之后将其存储到黑名单中作为判别依据,相较于存储完整的JWT字符串减少了存储开销。另外我们只需保证JWT在其有效期内用户登出后失效就可以了,JWT有效期过了黑名单也就没有存在的必要,所以我们这里还需要设置黑名单的过期时间,不然黑名单的数量会无休止的越来越多,这是我们不想看到的。

@Api(tags = "认证中心")
@RestController
@RequestMapping("/oauth")
@AllArgsConstructor
public class AuthController {

private RedisTemplate redisTemplate;

@DeleteMapping("/logout")
public Result logout(HttpServletRequest request) {
    String payload = request.getHeader(AuthConstants.JWT_PAYLOAD_KEY);
    JSONObject jsonObject = JSONUtil.parseObj(payload);

    String jti = jsonObject.getStr("jti"); // JWT唯一标识
    long exp = jsonObject.getLong("exp"); // JWT过期时间戳(单位:秒)

    long currentTimeSeconds = System.currentTimeMillis() / 1000;

    if (exp < currentTimeSeconds) { // token已过期
        return Result.custom(ResultCode.INVALID_TOKEN_OR_EXPIRED);
    }
    redisTemplate.opsForValue().set(AuthConstants.TOKEN_BLACKLIST_PREFIX + jti, null, (exp - currentTimeSeconds), TimeUnit.SECONDS);
    return Result.success();
}

}

  1. 网关(youlai-gateway)的全局过滤器
    从请求头提取JWT,解析出唯一标识jti,然后判断该标识是否存在黑名单列表里,如果是直接返回响应token失效的提示信息。

/**

  • 全局过滤器 黑名单token过滤
    */
    @Component
    @Slf4j
    @AllArgsConstructor
    public class AuthGlobalFilter implements GlobalFilter, Ordered {

    private RedisTemplate redisTemplate;

    @SneakyThrows
    @Override
    public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
    String token = exchange.getRequest().getHeaders().getFirst(AuthConstants.JWT_TOKEN_HEADER);
    if (StrUtil.isBlank(token)) {
    return chain.filter(exchange);
    }
    token = token.replace(AuthConstants.JWT_TOKEN_PREFIX, Strings.EMPTY);
    JWSObject jwsObject = JWSObject.parse(token);
    String payload = jwsObject.getPayload().toString();

     // 黑名单token(登出、修改密码)校验
     JSONObject jsonObject = JSONUtil.parseObj(payload);
     String jti = jsonObject.getStr("jti"); // JWT唯一标识
    
     Boolean isBlack = redisTemplate.hasKey(AuthConstants.TOKEN_BLACKLIST_PREFIX + jti);
     if (isBlack) {
         ServerHttpResponse response = exchange.getResponse();
         response.setStatusCode(HttpStatus.OK);
         response.getHeaders().set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
         response.getHeaders().set("Access-Control-Allow-Origin", "*");
         response.getHeaders().set("Cache-Control", "no-cache");
         String body = JSONUtil.toJsonStr(Result.custom(ResultCode.INVALID_TOKEN_OR_EXPIRED));
         DataBuffer buffer = response.bufferFactory().wrap(body.getBytes(Charset.forName("UTF-8")));
         return response.writeWith(Mono.just(buffer));
     }
    
     ServerHttpRequest request = exchange.getRequest().mutate()
             .header(AuthConstants.JWT_PAYLOAD_KEY, payload)
             .build();
     exchange = exchange.mutate().request(request).build();
     return chain.filter(exchange);
    

    }

    @Override
    public int getOrder() {
    return 0;
    }
    }

  1. 注销后JWT失效测试
    测试流程涉及到以下3个接口

  2. 登录访问资源

http://localhost:9999/youlai-auth/oauth/token
http://localhost:9999/youlai-admin/users/me

  1. 退出登录再次访问资源

http://localhost:9999/youlai-auth/oauth/logout
http://localhost:9999/youlai-admin/users/me
退出成功查看redis缓存黑名单列表

再次访问登录用户信息如下:

可以看到退出登录后再次使用原JWT请求提示“token无效或已过期”

  1. youlai-mall项目退出登录演示

上面报“token无效或已过期”的响应码是"A0230",这个对应的是Java开发手册【泰山版】的错误码

打开之前搭建好的前端管理平台youlai-mall-admin-web,修改src/util/request.js文件中的无效token的响应码为“A0230”,这样在token无效的情况下提示重新登录

演示通过第三方接口调试工具调用注销接口让JWT失效,然后再次刷新页面请求资源会因为JWT的失效而跳转到登录页。

四. 总结
JWT是JSON风格轻量级的授权和身份认证规范,可实现无状态、分布式应用的统一认证鉴权。但是事物往往具有两面性,有利必有弊,因为JWT的无状态,自生成后不借助外界条件唯一失效的方式就是过期。然而借助的外界的条件后JWT便有状态了的,也就是没有所谓严格意义上的无状态,其实也不必纠结于此,因为瑕不掩瑜。在白名单和黑名单的实现方式,这里选择了后者状态性更小的黑名单方式。还是文中提到过的一句话,如果你有更好的实现方式,欢迎留言告知,不胜感激!

标识jti,然后判断该标识是否存在黑名单列表

标签:java   时间戳   过期   success   The   etl   replace   一句话   fir   

原文地址:https://www.cnblogs.com/Aini999/p/13884203.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!