在微服务架构中,服务数量的增加导致身份验证和授权变得越来越复杂。为解决这个问题,通常采用 API 网关作为统一入口,负责请求路由、流量控制、安全认证等。本文将深入探讨如何在网关层集成 JWT (JSON Web Token) 身份认证,实现微服务统一认证,并分享实战中的一些避坑经验。
身份认证痛点:微服务面临的挑战
传统的单体应用中,身份验证通常由应用本身负责。但在微服务架构下,每个服务都可能需要进行身份验证,这导致了以下问题:
- 重复代码: 每个服务都需要实现身份验证逻辑,造成代码冗余。
- 安全风险: 各个服务的身份验证机制可能不一致,容易出现安全漏洞。
- 维护困难: 修改身份验证逻辑需要修改所有服务,维护成本高昂。
- 跨域问题: 前端应用需要与多个微服务交互,可能存在跨域请求问题。
JWT 认证原理与优势
JSON Web Token (JWT) 是一种基于 JSON 的开放标准 (RFC 7519),用于在各方之间安全地传输信息。一个 JWT 包含三个部分:
- Header (头部): 描述 JWT 的类型和使用的加密算法。
- Payload (载荷): 包含声明 (claims),例如用户身份信息、过期时间等。
- Signature (签名): 由 Header、Payload 和密钥使用指定的加密算法生成,用于验证 JWT 的完整性和真实性。
JWT 认证的优势在于:
- 无状态: 服务端不需要存储会话信息,减轻服务器压力,便于水平扩展。
- 安全: 使用数字签名保证 JWT 的完整性和真实性。
- 可扩展: 可以在 Payload 中添加自定义声明,满足不同的业务需求。
- 跨域友好: JWT 可以作为 Cookie 或 HTTP Header 传递,方便跨域请求。
Gateway 集成 JWT 身份认证的实践方案
我们以 Spring Cloud Gateway 为例,演示如何在网关层集成 JWT 身份认证。
1. 添加依赖
首先,在 pom.xml 文件中添加 Spring Cloud Gateway 和 JWT 相关依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
2. 创建 JWT 工具类
创建一个 JwtUtil 类,用于生成和解析 JWT:
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.Map;
@Component
public class JwtUtil {
@Value("${jwt.secret}") // 从 application.yml 读取 JWT 密钥
private String secret;
@Value("${jwt.expiration}") // 从 application.yml 读取 JWT 过期时间
private Long expiration;
// 生成 JWT
public String generateToken(Map<String, Object> claims) {
Date now = new Date();
Date expiryDate = new Date(now.getTime() + expiration * 1000);
return Jwts.builder()
.setClaims(claims)
.setIssuedAt(now)
.setExpiration(expiryDate)
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
}
// 解析 JWT
public Claims getClaimsFromToken(String token) {
try {
return Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody();
} catch (Exception e) {
return null; // JWT 解析失败
}
}
// 验证 JWT 是否过期
public boolean isTokenExpired(String token) {
Claims claims = getClaimsFromToken(token);
if (claims == null) {
return true; // JWT 无效,视为过期
}
Date expirationDate = claims.getExpiration();
return expirationDate.before(new Date());
}
}
3. 创建 Gateway Filter
创建一个 Gateway Filter,用于拦截请求并验证 JWT。你可以继承 AbstractGatewayFilterFactory 并实现 apply 方法。
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.Arrays;
import java.util.List;
@Component
public class JwtAuthGatewayFilterFactory extends AbstractGatewayFilterFactory<JwtAuthGatewayFilterFactory.Config> {
private final JwtUtil jwtUtil;
public JwtAuthGatewayFilterFactory(JwtUtil jwtUtil) {
super(Config.class);
this.jwtUtil = jwtUtil;
}
@Override
public GatewayFilter apply(Config config) {
return (exchange, chain) -> {
ServerHttpRequest request = exchange.getRequest();
// 从 Header 中获取 JWT
String token = request.getHeaders().getFirst("Authorization");
if (StringUtils.isEmpty(token) || !token.startsWith("Bearer ")) {
return onError(exchange, "Missing or invalid Authorization header", HttpStatus.UNAUTHORIZED);
}
token = token.substring(7); // 去掉 "Bearer " 前缀
if (jwtUtil.isTokenExpired(token)) {
return onError(exchange, "Token is expired", HttpStatus.UNAUTHORIZED);
}
// 可以从 JWT 中提取用户信息,并添加到请求头中,传递给下游服务
// Claims claims = jwtUtil.getClaimsFromToken(token);
// request = exchange.getRequest().mutate()
// .header("X-User-Id", claims.get("userId", String.class))
// .build();
return chain.filter(exchange.mutate().request(request).build());
};
}
private Mono<Void> onError(ServerWebExchange exchange, String err, HttpStatus httpStatus) {
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(httpStatus);
return response.setComplete();
}
public static class Config {
// 可以添加自定义配置项,例如需要放行的 URL 列表
private List<String> permitUrls = Arrays.asList("/auth/login", "/auth/register");
public List<String> getPermitUrls() {
return permitUrls;
}
public void setPermitUrls(List<String> permitUrls) {
this.permitUrls = permitUrls;
}
}
}
4. 配置路由规则
在 application.yml 中配置路由规则,并应用 JWT 认证 Filter:
spring:
cloud:
gateway:
routes:
- id: user-service
uri: lb://user-service # 使用服务发现,例如 Eureka
predicates:
- Path=/user/**
filters:
- JwtAuth
5. 客户端请求
客户端在请求时,需要在 HTTP Header 中添加 Authorization 字段,值为 Bearer <JWT>。例如:
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
实战避坑经验
- 密钥安全: JWT 密钥必须妥善保管,避免泄露。可以使用环境变量或配置中心管理密钥。
- 过期时间: 设置合理的 JWT 过期时间,避免 JWT 被长期滥用。过短的过期时间可能导致频繁刷新 Token,影响用户体验。
- 刷新 Token: 实现刷新 Token 机制,允许用户在 Token 过期前重新获取新的 Token,避免重新登录。
- 黑名单机制: 实现 JWT 黑名单机制,当用户注销或发生安全事件时,可以立即失效 JWT。
- 参数校验: 对 JWT 中的声明进行校验,确保数据的合法性。
- 性能优化: 如果网关的并发连接数很高,需要考虑 JWT 验证的性能,可以使用缓存等技术进行优化。
- 监控告警: 监控 JWT 的生成和验证情况,及时发现异常。
在实际项目中,可以结合 Nginx 的反向代理和负载均衡能力,构建高可用、高性能的 API 网关。还可以使用宝塔面板等工具简化服务器运维工作。通过 Gateway 集成 JWT 身份认证,可以实现微服务统一认证,提高系统的安全性和可维护性。
冠军资讯
代码一只喵