当前位置:首页 > 实用技巧 >

jwt如何清除(jwt过期怎么设置)

来源:原点资讯(www.yd166.com)时间:2023-06-09 08:19:20作者:YD166手机阅读>>

知识回顾

众所周知,在 OAuth2 体系中认证通过后返回的令牌信息分为两大类:不透明令牌(opaque tokens)透明令牌(not opaque tokens)。

不透明令牌 是一种无可读性的令牌,一般来说就是一段普通的 UUID 字符串。使用不透明令牌时资源服务不知道这个令牌是什么,代表谁,需要调用认证服务器校验、获取用户信息。使用不透明令牌采用的是 中心化 的架构。

透明令牌 一般指的是我们常说的JWT Token,用户信息保存在 JWT 字符串中,资源服务器自己可以解析令牌不再需要去认证服务器校验令牌。使用JWT是属于 无状态、去中心化的架构。

一旦我们选择了使用JWT,就需要明确一点:在不借助外力的情况下,让JWT失效的唯一途径就是等token自己过期,无法做到主动让JWT失效。非要让JWT有主动失效的功能只能借助外力,即在服务端存储JWT的状态,在请求时添加判断逻辑,这个与JWT的无状态化、去中心化特性是矛盾的。但是,既然选择了JWT这条路,那就只能接受这个现实。

tips:我们目前项目使用的是JWT Token这种去中心化的架构,并且独立出了一个统一的资源服务器配置模块,详情可见:SpringCloud Alibaba微服务实战三十 | 统一资源服务器配置模块。

解决思路

上面说了,要实现JWT的主动失效需要借助外力,在服务端存储JWT的状态,一般使用Redis等高速缓存。而存储JWT状态又分为两种方案:

  1. 白名单机制认证通过时,把JWT存到Redis中。注销时,从缓存移除JWT。请求资源添加判断JWT在缓存中是否存在,不存在拒绝访问。这种方式和cookie/session机制中的会话失效删除session基本一致。
  2. 黑名单机制注销登录时,缓存JWT至Redis,且缓存有效时间设置为JWT的有效期,请求资源时判断是否存在缓存的黑名单中,存在则拒绝访问。

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

我更倾向于使用黑名单机制,有两个原因:

一是会大大节省Redis的存储空间,我们甚至都不需要存储完整的jwt,只需要存储jwt中的唯一id jti即可。

jwt如何清除,jwt过期怎么设置(1)

二是我们有独立的资源服务器配置模块,使用白名单的话那就要求所有依赖的业务模块必须加入Redis依赖、添加Redis的配置,不太友好。采用黑名单的话我们只需要在网关层做校验,也就是只要网关和认证服务器添加redis依赖即可。

OK,既然确定了使用黑名单机制,那我们最后再来梳理一下完整的实现流程:

  1. 认证服务器需要添加一个退出登录接口,在退出登录时将jwt 的唯一id jti添加进redis,并设置有效时间为token的剩余时间。
  2. 所有请求需要经过网关,网关层通过Filter添加校验逻辑,解析并判断用户携带的token jti是否在redis中。若存在,说明该token已失效,拒绝访问;否则放行。

梳理好了实现流程,我们开始代码改造。

代码改造

注意,为了不影响阅读,文中展示的代码仅为与此文主题相关的代码,其他无关代码以...代替。

认证服务器改造
  1. 由于需要用到redis存储黑名单,所以需要引入redis依赖

<dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-redis</artifactId> </dependency>

  1. 修改nacos配置中心auth-service.yml,配置redis相关属性

spring: redis: host: localhost password: xxxxxx port: 6379 timeout: 3000

  1. 编写退出接口,将token插入黑名单

@RestController @RequestMapping("/token") @Slf4j @RequiredArgsConstructor(onConstructor = @__(@Autowired)) public class AuthController { private final TokenStore tokenStore; private final redisTemplate<String,String> redisTemplate; /** * 用户退出登录 * @param authHeader 从请求头获取token */ @DeleteMapping("/logout") public ResultData<String> logout(@RequestHeader(value = HttpHeaders.AUTHORIZATION) String authHeader){ //获取token,去除前缀 String token = authHeader.replace(OAuth2AccessToken.BEARER_TYPE,"").trim(); // 解析Token OAuth2AccessToken oAuth2AccessToken = tokenStore.readAccessToken(token); //token 已过期 if(oAuth2AccessToken.isExpired()){ return ResultData.fail(ReturnCode.INVALID_TOKEN_OR_EXPIRED.getCode(),ReturnCode.INVALID_TOKEN_OR_EXPIRED.getMessage()); } if(StringUtils.isBlank(oAuth2AccessToken.getValue())){ //访问令牌不合法 return ResultData.fail(ReturnCode.INVALID_TOKEN.getCode(),ReturnCode.INVALID_TOKEN.getMessage()); } OAuth2Authentication oAuth2Authentication = tokenStore.readAuthentication(oAuth2AccessToken); String userName = oAuth2Authentication.getName(); //获取token唯一标识 String jti = (String) oAuth2AccessToken.getAdditionalInformation().get("jti"); //获取过期时间 Date expiration = oAuth2AccessToken.getExpiration(); long exp = expiration.getTime() / 1000; long currentTimeSeconds = System.currentTimeMillis() / 1000; //设置token过期时间 redisTemplate.opsForValue().set(CloudConstant.TOKEN_BLACKLIST_PREFIX jti, userName, (exp - currentTimeSeconds), TimeUnit.SECONDS); return ResultData.success("退出成功"); } }

在认证服务器可以通过OAuth2AccessToken直接解析token,对token的有效性做一些基本校验,计算token的剩余有效时间并通过redisTemplate加入redis。

插入黑名单的时候并没有把完整的jwt token直接插入,而是使用了jwt的唯一标识jti,用于节省redis存储空间。

  1. 修改认证服务器配置文件WebSecurityConfig,给退出接口放行

@EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) //@Configuration public class WebSecurityConfig extends WebSecurityConfigurerAdapter { ... /** * http安全配置 * @param http http安全对象 * @throws Exception http安全异常信息 */ @Override protected void configure(HttpSecurity http) throws Exception { // 加入验证码登陆 http.apply(smsCodeSecurityConfig); http .authorizeRequests().requestMatchers(EndpointRequest.toAnyEndpoint()).permitAll() .and() .authorizeRequests().antMatchers("/token/**","/sms/**").permitAll() .anyRequest().authenticated() .and() .csrf().disable(); } ... }

至此认证服务器改造完毕,接下来对网关进行改造。

网关改造

网关层不再引入oauth2相关的依赖,所以就不能使用认证服务器的方法来解析token了,这里我使用的是nimbus-jose-jwt这个工具类。

  1. 引入nimbus-jose-jwt依赖

<dependency> <groupId>com.nimbusds</groupId> <artifactId>nimbus-jose-jwt</artifactId> <version>9.13</version> </dependency>

  1. 引入Redis 依赖并且配置Redis相关属性

这个已经在认证服务器改造的时候配置过,就不重复了。

  1. 修改网关过滤器GatewayRequestFilter,判断token是否存在于黑名单

@Component @Order(0) @Slf4j @RequiredArgsConstructor(onConstructor = @__(@Autowired)) public class GatewayRequestFilter implements GlobalFilter { private final RedisTemplate<String,String> redisTemplate; @SneakyThrows @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { ... //获取请求头的token String headerToken = exchange.getRequest().getHeaders().getFirst(CloudConstant.JWT_HEADER_KEY); if (StrUtil.isNotEmpty(headerToken)) { // 是否在黑名单 if(isBlack(headerToken)){ throw new HttpServerErrorException(HttpStatus.FORBIDDEN,"该令牌已过期,请重新获取令牌"); } } ... return chain.filter(newExchange); } /** * 通过redis判断token是否为黑名单 * @param headerToken 请求头 * @return boolean */ private boolean isBlack(String headerToken) throws ParseException { //todo 移除所有oauth2相关代码,暂时使用 OAuth2AccessToken.BEARER_TYPE 代替 String token = headerToken.replace(OAuth2AccessToken.BEARER_TYPE, StrUtil.EMPTY).trim(); //解析token JWSObject jwsObject = JWSObject.parse(token); String payload = jwsObject.getPayload().toString(); JSONObject jsonObject = JSONUtil.parseObj(payload); // JWT唯一标识 String jti = jsonObject.getStr("jti"); return redisTemplate.hasKey(CloudConstant.TOKEN_BLACKLIST_PREFIX jti); } ... }

至此网关层也改造完毕,接下来我们测试一下完整流程。

测试
  1. 执行退出登录

jwt如何清除,jwt过期怎么设置(2)

jwt如何清除,jwt过期怎么设置(3)

退出登录后在Redis中可以看到黑名单,key为jti,value为登录用户。

  1. 使用过期的token访问其他接口会被网关拦截

jwt如何清除,jwt过期怎么设置(4)

小结

一般来说,既然选择了jwt那就要接受jwt这个不允许主动失效的约定,如果考虑到安全性,可以通过缩短jwt有效时间,采用https等手段进行防护。虽然我们通过代码改造让其实现了主动失效的功能,但最终还是失去了jwt 无状态化、去中心化的特性。当然了,所有的方案都不可能十全十美,主要是看哪种更符合你们的业务场景。

如果,你有更好的实现方式,欢迎留言告知,不胜感激!

栏目热文

怀旧服战场经验(怀旧服战场经验修复)

怀旧服战场经验(怀旧服战场经验修复)

暴雪在今天凌晨发布了蓝贴宣布了巫妖王之怒怀旧服P3阶段开放日程表,下面带大家一起解读蓝贴内容,文中提及的时间仅适用于亚服...

2023-06-09 08:07:52查看全文 >>

怀旧服如何开启战场(怀旧服怎么开启各种信息)

怀旧服如何开启战场(怀旧服怎么开启各种信息)

魔兽世界怀旧服pvp荣誉系统已经上线,战场也将在12月10日上线奥特兰克山谷战场和战歌峡谷战场,那么这个战歌峡谷战场部落...

2023-06-09 08:12:50查看全文 >>

怀旧服怎么防掉出战场(怀旧服战场怎么排那么长时间)

怀旧服怎么防掉出战场(怀旧服战场怎么排那么长时间)

《魔兽世界怀旧服》目前排队情况很严重,很多服务器要排队七八个小时。这里我们为朋友们分享一个全职业防暂离掉线方法,让你上线...

2023-06-09 08:10:33查看全文 >>

怀旧服怎么不能打战场(怀旧服现在有打战场的吗)

怀旧服怎么不能打战场(怀旧服现在有打战场的吗)

任何一名魔兽世界怀旧服玩家遭遇封号的时候都会感觉非常郁闷,同时也会回忆自己有没有做出违规的事情。如果没有的话,就可以找G...

2023-06-09 07:58:51查看全文 >>

怀旧服战场怎么锁经验(怀旧服战场怎么进)

怀旧服战场怎么锁经验(怀旧服战场怎么进)

魔兽世界WLK怀旧服距离玩家是越来越近,暴雪也在抓紧时间制作WLK怀旧服,准备给玩家一份满意的答卷。今天暴雪发布蓝贴说明...

2023-06-09 07:49:53查看全文 >>

怎么取出jwt里的数据(jwt怎么获取额外信息)

怎么取出jwt里的数据(jwt怎么获取额外信息)

前文《》介绍了目前应用最多的会话管理方案还是基于token的方案,不仅有很多实现,而且还有现成的标准可用,这个标准就是J...

2023-06-09 07:36:00查看全文 >>

删除开始菜单文档中的历史记录(怎么删除开始菜单中的项目)

删除开始菜单文档中的历史记录(怎么删除开始菜单中的项目)

你是否有过这样的操作,你用公司电脑打印个人简历,而你又不想让别人知道你的这个操作,想删除电脑里面的记录。下面教你如何操作...

2023-06-09 07:39:46查看全文 >>

jwt过期了怎么处理(jwt过期时间存储在哪)

jwt过期了怎么处理(jwt过期时间存储在哪)

前面一篇文章《》介绍了基于SpringBoot实现JWT登录认证的基本思路,我们使用了com.auth0 这样一个JWT...

2023-06-09 08:07:42查看全文 >>

如何删除运行中的记录(删除运行里的记录)

如何删除运行中的记录(删除运行里的记录)

通常,在电脑使用过程中会自动记录一些用户的使用信息,这涉及面很广,比如今天你上网浏览了哪些内容?还比如你今天是否登录了你...

2023-06-09 07:55:04查看全文 >>

jwt如何去掉(jwt过期怎么设置)

jwt如何去掉(jwt过期怎么设置)

JWT 引入JSONwebtoken(JWT)是一个开放标准(rfc7519),它定义了一种紧凑的、自包含的方式,用于在...

2023-06-09 07:47:42查看全文 >>

文档排行