SpringCloud Alibaba 实战:如何让 jwt token 主动失效?

本文介绍如何在SpringCloud Alibaba项目中处理JWT token失效问题,通过黑名单机制实现JWT的主动失效。讨论了白名单和黑名单的优缺点,并详细讲解了代码改造过程,包括认证服务器和网关的改动,以及测试验证。

 前言

有一个看我SpringCloud alibaba系列文章的粉丝私下问我:如何处理jwt失效的问题?修改密码或退出登录后需要将之前的jwt token失效掉,不允许使用旧token登录系统。

我说:很简单呀,咱们直接 无为而治,用户退出或修改密码的时候前端直接删除这个token不就完了吗?后端啥都不用管,啥也不用做。

他说:别闹,你的每篇文章我都给你一键三连。

我当时就被感动了,既然是这样的好读者,我果断答应专门给他写篇文章来分享一下我这个不太成熟的做法,改造一下这个SpringCloud alibaba项目。

在正式开始之前,我们先来回顾一下oauth2中token的相关知识。

知识回顾

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

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

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

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

解决思路

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

  1. 白名单机制

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

  2. 黑名单机制

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

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

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

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

<think>我们正在讨论SpringCloudGateway中实现JWT鉴权配置。根据引用内容,我们可以总结出以下关键点:1.需要添加SpringCloudGateway和JWT相关依赖(如java-jwt和fastjson)。2.需要创建JWT工具类(JwtUtil)来处理JWT的生成和验证。3.需要创建一个全局过滤器(如JwtTokenFilter)来拦截请求并验证JWT。4.可以通过注解(如@JwtCheck)和AOP(如JwtCheckAop)来实现更细粒度的控制(可选,根据需求)。5.配置路由,确保请求经过网关时进行JWT验证。具体实现步骤:步骤1:添加依赖在pom.xml中添加SpringCloudGateway和JWT相关依赖。步骤2:创建JWT工具类(JwtUtil)这个类负责生成JWT、验证JWT和解析JWT。可以使用java-jwt库。步骤3:创建全局过滤器(JwtTokenFilter)实现GlobalFilter接口,在过滤器中检查请求的Header中是否包含有效的JWT。如果验证失败,则返回401错误。步骤4:注册过滤器将创建的全局过滤器注册为SpringBean,这样它就会自动生效。步骤5:配置路由(可选,根据实际路由配置)在application.yml中配置路由规则,确保需要鉴权的请求路径被正确路由。下面我们详细展开:###步骤1:添加依赖在项目的pom.xml文件中添加以下依赖:```xml<!--SpringCloudGateway--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-gateway</artifactId></dependency><!--JWT--><dependency><groupId>com.auth0</groupId><artifactId>java-jwt</artifactId><version>3.4.0</version></dependency><!--fastjson(可选,用于JSON处理)--><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.62</version></dependency>```###步骤2:创建JWT工具类创建一个JwtUtil类,包含生成Token、验证Token和解析Token的方法。示例代码如下:```javaimportcom.auth0.jwt.JWT;importcom.auth0.jwt.JWTVerifier;importcom.auth0.jwt.algorithms.Algorithm;importcom.auth0.jwt.exceptions.JWTVerificationException;importcom.auth0.jwt.interfaces.DecodedJWT;importjava.util.Date;publicclassJwtUtil{//秘钥(应保存在配置文件中,此处为示例)privatestaticfinalStringSECRET="your_secret_key";//创建TokenpublicstaticStringcreateToken(StringuserId){returnJWT.create().withSubject(userId).withExpiresAt(newDate(System.currentTimeMillis()+3600_000))//1小时.sign(Algorithm.HMAC256(SECRET));}//验证TokenpublicstaticbooleanverifyToken(Stringtoken){try{JWTVerifierverifier=JWT.require(Algorithm.HMAC256(SECRET)).build();verifier.verify(token);returntrue;}catch(JWTVerificationExceptione){returnfalse;}}//解析Token,获取用户IDpublicstaticStringgetUserId(Stringtoken){try{DecodedJWTjwt=JWT.decode(token);returnjwt.getSubject();}catch(Exceptione){returnnull;}}}```###步骤3:创建全局过滤器创建一个全局过滤器`JwtTokenFilter`,实现`GlobalFilter`接口。在该过滤器中,我们将检查请求头中的Authorization字段(即JWT),并进行验证。```javaimportorg.springframework.cloud.gateway.filter.GlobalFilter;importorg.springframework.core.Ordered;importorg.springframework.http.HttpStatus;importorg.springframework.stereotype.Component;importorg.springframework.web.server.ServerWebExchange;importreactor.core.publisher.Mono;@ComponentpublicclassJwtTokenFilterimplementsGlobalFilter,Ordered{@OverridepublicMono<Void>filter(ServerWebExchangeexchange,GatewayFilterChainchain){Stringtoken=exchange.getRequest().getHeaders().getFirst("Authorization");//如果请求路径是登录或公开的,则放行(根据实际情况调整)Stringpath=exchange.getRequest().getURI().getPath();if(path.startsWith("/auth/login")){returnchain.filter(exchange);}if(token==null||!token.startsWith("Bearer")){exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);returnexchange.getResponse().setComplete();}//提取Tokentoken=token.substring(7);if(!JwtUtil.verifyToken(token)){exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);returnexchange.getResponse().setComplete();}//将用户ID放入请求头中,传递给下游服务StringuserId=JwtUtil.getUserId(token);exchange.getRequest().mutate().header("X-User-Id",userId).build();returnchain.filter(exchange);}@OverridepublicintgetOrder(){return-100;//设置过滤器的优先级,数值越小优先级越高}}```###步骤4:配置路由在application.yml中配置路由,例如:```yamlspring:cloud:gateway:routes:-id:auth-serviceuri:http://localhost:8081#替换为你的认证服务地址predicates:-Path=/auth/**-id:user-serviceuri:http://localhost:8082#替换为你的用户服务地址predicates:-Path=/user/**filters:-StripPrefix=1```###注意事项1.上述代码中,我们假设登录接口(如`/auth/login`)是不需要JWT验证的,其他接口都需要。2.在过滤器中,我们验证JWT后,将用户ID放入请求头`X-User-Id`中,这样下游服务可以直接使用。3.实际项目中,秘钥(SECRET)应该放在配置文件中,而不是硬编码。4.根据实际需求,可以添加更复杂的验证逻辑,如角色验证(在Token中加入角色信息,然后在过滤器中验证)。###相关问题1.如何实现JWT的刷新机制?2.在SpringCloudGateway中如何处理JWT失效的情况?3.如何将JWT鉴权与SpringSecurity结合使用?</think>### Spring Cloud Gateway JWT 鉴权配置实现 在Spring Cloud Gateway中实现JWT鉴权需要以下核心步骤: #### 1. **添加依赖** 在`pom.xml`中添加JWT和网关依赖: ```xml <!-- Spring Cloud Gateway --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> <!-- JWT 支持 --> <dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>3.18.2</version> <!-- 使用最新稳定版 --> </dependency> ``` #### 2. **创建JWT工具类** ```java import com.auth0.jwt.JWT; import com.auth0.jwt.algorithms.Algorithm; import com.auth0.jwt.exceptions.JWTVerificationException; import com.auth0.jwt.interfaces.DecodedJWT; public class JwtUtil { private static final String SECRET = "your_secret_key"; // 应配置在安全位置 private static final Algorithm ALGORITHM = Algorithm.HMAC256(SECRET); // 生成Token public static String generateToken(String userId) { return JWT.create() .withSubject(userId) .sign(ALGORITHM); } // 验证Token public static boolean verifyToken(String token) { try { JWT.require(ALGORITHM).build().verify(token); return true; } catch (JWTVerificationException e) { return false; } } // 解析Token public static DecodedJWT parseToken(String token) { return JWT.require(ALGORITHM).build().verify(token); } } ``` #### 3. **实现全局过滤器** 创建`JwtAuthFilter`处理JWT验证: ```java import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.core.Ordered; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; @Component public class JwtAuthFilter implements GlobalFilter, Ordered { @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { // 1. 排除登录等公开路径 if (exchange.getRequest().getURI().getPath().contains("/auth/login")) { return chain.filter(exchange); } // 2. 获取Token String token = exchange.getRequest().getHeaders().getFirst("Authorization"); if (token == null || !token.startsWith("Bearer ")) { exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED); return exchange.getResponse().setComplete(); } // 3. 验证Token token = token.substring(7); // 移除"Bearer " if (!JwtUtil.verifyToken(token)) { exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED); return exchange.getResponse().setComplete(); } // 4. 将用户信息传递给下游服务 DecodedJWT jwt = JwtUtil.parseToken(token); exchange.getRequest().mutate() .header("X-User-Id", jwt.getSubject()) .build(); return chain.filter(exchange); } @Override public int getOrder() { return -100; // 高优先级执行 } } ``` #### 4. **配置路由规则** 在`application.yml`中配置需要鉴权的路由: ```yaml spring: cloud: gateway: routes: - id: user-service uri: lb://user-service predicates: - Path=/user/** filters: - name: JwtAuthFilter # 应用JWT过滤器 ``` #### 5. **添加登录接口** 在认证服务中实现登录接口生成JWT: ```java @RestController @RequestMapping("/auth") public class AuthController { @PostMapping("/login") public ResponseEntity<?> login(@RequestBody UserDTO user) { // 验证用户名密码(伪代码) if (authenticate(user)) { String token = JwtUtil.generateToken(user.getId()); return ResponseEntity.ok(Collections.singletonMap("token", token)); } return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); } } ``` ### 关键配置说明 1. **安全增强**: - 将JWT秘钥存储在配置中心(如Spring Cloud Config) - 使用HTTPS传输Token - 设置合理的Token过期时间(通过`JwtUtil.generateToken()`实现) 2. **路径排除**: - 在过滤器中配置白名单路径(如`/auth/**`、`/public/**`) - 使用`exchange.getRequest().getURI().getPath()`进行路径匹配 3. **下游服务集成**: - 通过请求头`X-User-Id`传递用户身份 - 可扩展添加角色信息(在JWT Claims中添加角色字段) ### 常见问题解决方案 - **Token失效处理**:返回401状态码并引导客户端重新登录 - **跨域问题**:在网关层配置CORS规则 - **性能优化**:使用Redis缓存已验证的Token 此方案实现了网关层的统一认证,符合微服务架构的安全要求[^1][^2]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值