在微服务架构中,网关是所有请求的入口,承担着请求路由、负载均衡、限流熔断、统一鉴权等核心功能。本章节将深入探讨如何在网关层实现统一的安全认证和授权管理,涵盖 JWT 令牌处理、OAuth2.0 协议集成、接口权限控制、白名单管理等关键内容。通过本章的学习,读者将掌握 Spring Cloud Gateway 的高级配置技巧,实现企业级的安全防护体系。
18.2.1 网关在架构中的位置
网关是微服务架构的前端门神,所有外部请求都需要经过网关才能到达后端服务。网关的核心职责包括:请求路由根据 URL 路径或请求特征将请求转发到对应的后端服务;负载均衡在路由基础上实现多实例的负载分发;安全认证在网关层统一处理身份验证和授权;限流熔断保护后端服务免受过载影响;日志监控记录所有请求的详细信息便于问题排查。
┌─────────────────────────────────────────────────────────────────────┐
│ 客户端请求 │
│ 移动端 / Web端 / 第三方 │
└───────────────────────────────┬─────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ 安 全 防 护 层 │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌───────────┐ │
│ │ WAF │ │ DDoS │ │ API │ │ 认证 │ │
│ │ 防火墙 │ │ 防护 │ │ 限流 │ │ 授权 │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ └───────────┘ │
└───────────────────────────────┬─────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ Spring Cloud Gateway │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Route: /api/user/** → user-service:8080 │ │
│ │ Route: /api/order/** → order-service:8080 │ │
│ │ Route: /api/product/** → product-service:8080 │ │
│ │ Route: /api/ai/** → ai-gateway-service:8080 │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Global Filter: Token验证 / 权限检查 / 日志记录 / 监控指标 │ │
│ └─────────────────────────────────────────────────────────────┘ │
└───────────────────────────────┬─────────────────────────────────────┘
│
┌───────────────────────┼───────────────────────┐
│ │ │
▼ ▼ ▼
┌───────────────┐ ┌───────────────┐ ┌───────────────┐
│ User Service │ │ Order Service │ │Product Service│
│ 8081 │ │ 8082 │ │ 8083 │
└───────────────┘ └───────────────┘ └───────────────┘
18.2.2 网关技术选型
在 Java 生太中,常用的网关解决方案包括 Spring Cloud Gateway、Kong、Zuul 等。Spring Cloud Gateway 基于 Spring 5 和 Project Reactor 构建,具有高性能、低延迟的特点,是 Spring Cloud 生态的首选。相比于Zuul 1.x 的同步阻塞模型,Spring Cloud Gateway 采用响应式编程模型,性能有显著提升。
|
特性 |
Spring Cloud Gateway |
Kong |
Zuul 2.x |
|
------ |
--------------------- |
------ |
---------- |
|
性能 |
高(响应式) |
高 |
中等 |
|
生态集成 |
完美集成 Spring Cloud |
独立部署 |
需要额外配置 |
|
配置方式 |
代码 + YML |
Admin API + YML |
代码配置 |
|
插件支持 |
需自行开发 |
丰富的插件市场 |
有限 |
|
学习成本 |
中等 |
较高 |
中等 |
18.3.1 JWT 令牌处理机制
JWT(JSON Web Token)是一种开放标准,用于在各方之间安全地传输信息。JWT 由三部分组成:Header(头部)、Payload(负载)和 Signature(签名)。在网关层,我们需要实现 JWT 的解析、验证和提取用户信息等功能。
@Component
@Slf4j
public class JwtAuthenticationFilter implements GlobalFilter, Ordered {
private static final String BEARER_PREFIX = "Bearer ";
private static final String AUTHORIZATION_HEADER = "Authorization";
private static final String USER_INFO_KEY = "userInfo";
@Autowired
private JwtTokenProvider jwtTokenProvider;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private WhiteListConfig whiteListConfig;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String path = exchange.getRequest().getURI().getPath();
// 白名单路径直接放行
if (whiteListConfig.isWhiteListed(path)) {
return chain.filter(exchange);
}
// 获取令牌
String token = extractToken(exchange.getRequest());
if (StringUtils.isEmpty(token)) {
return unauthorized(exchange, "未提供认证令牌");
}
try {
// 验证令牌
if (!jwtTokenProvider.validateToken(token)) {
return unauthorized(exchange, "令牌无效或已过期");
}
// 检查令牌是否在黑名单中
if (isTokenBlacklisted(token)) {
return unauthorized(exchange, "令牌已被吊销");
}
// 提取用户信息
UserPrincipal userInfo = jwtTokenProvider.getUserInfo(token);
// 将用户信息传递给下游服务
ServerWebExchange modifiedExchange = addUserInfoToHeader(exchange, userInfo);
// 记录访问日志
logAccess(exchange, userInfo);
return chain.filter(modifiedExchange);
} catch (Exception e) {
log.error("JWT 验证失败: {}", e.getMessage());
return unauthorized(exchange, "认证失败:" + e.getMessage());
}
}
private String extractToken(ServerHttpRequest request) {
String bearerToken = request.getHeaders().getFirst(AUTHORIZATION_HEADER);
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith(BEARER_PREFIX)) {
return bearerToken.substring(BEARER_PREFIX.length());
}
return null;
}
private boolean isTokenBlacklisted(String token) {
String blacklistKey = "token:blacklist:" + jwtTokenProvider.getTokenId(token);
return Boolean.TRUE.equals(redisTemplate.hasKey(blacklistKey));
}
private ServerWebExchange addUserInfoToHeader(ServerWebExchange exchange, UserPrincipal userInfo) {
String userInfoJson = JsonUtil.toJson(userInfo);
String userInfoBase64 = Base64.getEncoder().encodeToString(userInfoJson.getBytes());
ServerHttpRequest mutatedRequest = exchange.getRequest().mutate()
.header("X-User-Id", userInfo.getUserId())
.header("X-User-Name", userInfo.getUserName())
.header("X-User-Info", userInfoBase64)
.build();
return exchange.mutate().request(mutatedRequest).build();
}
private Mono<Void> unauthorized(ServerWebExchange exchange, String message) {
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.UNAUTHORIZED);
response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
Result<String> result = Result.error(401, message);
String body = JsonUtil.toJson(result);
DataBuffer buffer = response.bufferFactory().wrap(body.getBytes());
return response.writeWith(Mono.just(buffer));
}
@Override
public int getOrder() {
return -100; // 优先级最高
}
}
18.3.2 令牌刷新与续期机制
Access Token 的有效期通常较短,需要通过 Refresh Token 来实现无感知的令牌刷新。我们设计了双令牌机制:Access Token 有效期为 15 分钟,用于日常 API 访问;Refresh Token 有效期为 7 天,用于获取新的 Access Token。
@Service
@Slf4j
public class TokenRefreshService {
private static final long ACCESS_TOKEN_VALIDITY = 15 * 60 * 1000; // 15分钟
private static final long REFRESH_TOKEN_VALIDITY = 7 * 24 * 60 * 60 * 1000; // 7天
@Autowired
private JwtTokenProvider jwtTokenProvider;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
/**
* 刷新访问令牌
* @param refreshToken 刷新令牌
* @return 新的令牌对
*/
public TokenPair refreshAccessToken(String refreshToken) {
// 验证刷新令牌
if (!jwtTokenProvider.validateToken(refreshToken)) {
throw new TokenException("刷新令牌无效或已过期");
}
// 检查刷新令牌是否在黑名单
if (isRefreshTokenBlacklisted(refreshToken)) {
throw new TokenException("刷新令牌已被使用或吊销");
}
// 获取用户信息
UserPrincipal userInfo = jwtTokenProvider.getUserInfo(refreshToken);
// 吊销旧的刷新令牌(一次性使用)
blacklistRefreshToken(refreshToken);
// 生成新的令牌对
String newAccessToken = jwtTokenProvider.generateAccessToken(userInfo);
String newRefreshToken = jwtTokenProvider.generateRefreshToken(userInfo);
// 存储新的刷新令牌
storeRefreshToken(userInfo.getUserId(), newRefreshToken);
return new TokenPair(newAccessToken, newRefreshToken);
}
/**
* 检查 access token 是否即将过期,如果即将过期则自动续期
*/
public String checkAndRefreshAccessToken(String accessToken, String refreshToken) {
if (jwtTokenProvider.isTokenExpiringSoon(accessToken)) {
log.info("Access Token 即将过期,自动刷新");
TokenPair newTokens = refreshAccessToken(refreshToken);
return newTokens.getAccessToken();
}
return accessToken;
}
/**
* 计算令牌的剩余有效时间
*/
public long getTokenRemainingTime(String token) {
return jwtTokenProvider.getExpirationTime(token) - System.currentTimeMillis();
}
}
18.4.1 基于角色的访问控制
RBAC(Role-Based Access Control)是企业级应用中最常用的权限模型。我们定义了四种标准角色:超级管理员拥有系统所有权限;普通管理员拥有大部分管理权限;普通用户拥有基本的业务操作权限;访客只有只读权限。
/**
* 权限枚举
*/
public enum Permission {
// 用户相关权限
USER_READ("user:read", "查看用户"),
USER_CREATE("user:create", "创建用户"),
USER_UPDATE("user:update", "更新用户"),
USER_DELETE("user:delete", "删除用户"),
// 订单相关权限
ORDER_READ("order:read", "查看订单"),
ORDER_CREATE("order:create", "创建订单"),
ORDER_UPDATE("order:update", "更新订单"),
ORDER_CANCEL("order:cancel", "取消订单"),
// AI 服务相关权限
AI_CHAT("ai:chat", "使用 AI 聊天"),
AI_IMAGE("ai:image", "生成 AI 图片"),
AI_ANALYSIS("ai:analysis", "使用 AI 分析"),
// 系统管理权限
SYSTEM_CONFIG("system:config", "系统配置"),
SYSTEM_LOGS("system:logs", "查看系统日志");
private final String code;
private final String description;
Permission(String code, String description) {
this.code = code;
this.description = description;
}
}
/**
* 角色定义
*/
public class RolePermissions {
public static final Map<String, Set<Permission>> ROLE_PERMISSIONS = Map.of(
"ROLE_SUPER_ADMIN", Set.of(Permission.values()),
"ROLE_ADMIN", Set.of(
Permission.USER_READ, Permission.USER_CREATE, Permission.USER_UPDATE,
Permission.ORDER_READ, Permission.ORDER_CREATE, Permission.ORDER_UPDATE, Permission.ORDER_CANCEL,
Permission.AI_CHAT, Permission.AI_IMAGE, Permission.AI_ANALYSIS,
Permission.SYSTEM_LOGS
),
"ROLE_USER", Set.of(
Permission.USER_READ, Permission.USER_UPDATE,
Permission.ORDER_READ, Permission.ORDER_CREATE,
Permission.AI_CHAT, Permission.AI_IMAGE
),
"ROLE_GUEST", Set.of(
Permission.USER_READ,
Permission.ORDER_READ,
Permission.AI_CHAT
)
);
}
18.4.2 接口权限校验实现
在网关层实现权限校验可以统一处理所有接口的权限验证,无需在每个微服务中重复实现。
@Component
@Slf4j
public class AuthorizationFilter implements GlobalFilter, Ordered {
@Autowired
private AuthorizationService authorizationService;
@Autowired
private RouteDefinitionLocator routeDefinitionLocator;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String path = exchange.getRequest().getURI().getPath();
String method = exchange.getRequest().getMethod().name();
// 获取用户信息
String userId = exchange.getRequest().getHeaders().getFirst("X-User-Id");
if (StringUtils.isEmpty(userId)) {
// 用户信息为空,在认证过滤器中已处理
return chain.filter(exchange);
}
// 构建权限Key
String permissionKey = buildPermissionKey(path, method);
// 权限校验
return authorizationService.checkPermission(userId, permissionKey)
.flatMap(hasPermission -> {
if (hasPermission) {
return chain.filter(exchange);
} else {
log.warn("用户 {} 缺少权限 {}", userId, permissionKey);
return forbidden(exchange, "权限不足");
}
})
.onErrorResume(e -> {
log.error("权限校验异常: {}", e.getMessage());
return serverError(exchange, "权限校验失败");
});
}
/**
* 根据路径和方法构建权限Key
* 例如: /api/user/123 -> user:*
*/
private String buildPermissionKey(String path, String method) {
// 移除前缀
String cleanPath = path.replace("/api/", "");
// 提取服务名和资源名
String[] parts = cleanPath.split("/");
if (parts.length >= 2) {
String serviceName = parts[0];
String resource = parts.length > 1 ? parts[1] : "";
// 通配符处理,移除 ID 部分
resource = resource.replaceAll("\\d+", "*");
return serviceName + ":" + resource;
}
return cleanPath.replace("/", ":");
}
private Mono<Void> forbidden(ServerWebExchange exchange, String message) {
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.FORBIDDEN);
response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
Result<String> result = Result.error(403, message);
String body = JsonUtil.toJson(result);
DataBuffer buffer = response.bufferFactory().wrap(body.getBytes());
return response.writeWith(Mono.just(buffer));
}
@Override
public int getOrder() {
return -90; // 在认证过滤器之后执行
}
}
18.5.1 IP 白名单与黑名单
对于敏感接口或管理接口,我们通常需要配置 IP 白名单或黑名单来限制访问来源。IP 白名单允许只有指定 IP 或 IP 段的请求访问;IP 黑名单拒绝所有来自指定 IP 或 IP 段的请求。
@Component
@Slf4j
public class IpFilter implements GlobalFilter, Ordered {
@Autowired
private IpRuleConfig ipRuleConfig;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
private static final IpAddressParser IP_ADDRESS_PARSER = new IpAddressParser();
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String clientIp = getClientIp(exchange.getRequest());
// 检查黑名单
if (ipRuleConfig.isBlacklisted(clientIp)) {
log.warn("IP {} 在黑名单中,拒绝访问", clientIp);
return forbidden(exchange, "IP 被禁止访问");
}
// 如果配置了白名单,检查是否在白名单中
if (ipRuleConfig.hasWhitelist() && !ipRuleConfig.isWhitelisted(clientIp)) {
log.warn("IP {} 不在白名单中,拒绝访问", clientIp);
return forbidden(exchange, "IP 不在允许范围内");
}
return chain.filter(exchange);
}
/**
* 从请求中获取客户端真实 IP
* 需要考虑代理服务器的情况
*/
private String getClientIp(ServerHttpRequest request) {
// 优先从 X-Forwarded-For 头获取
String xff = request.getHeaders().getFirst("X-Forwarded-For");
if (StringUtils.hasText(xff)) {
// X-Forwarded-For 可能包含多个 IP,取第一个
return xff.split(",")[0].trim();
}
// 从 X-Real-IP 头获取
String xri = request.getHeaders().getFirst("X-Real-IP");
if (StringUtils.hasText(xri)) {
return xri;
}
// 直接从连接获取
InetSocketAddress remoteAddress = request.getRemoteAddress();
return remoteAddress != null ? remoteAddress.getAddress().getHostAddress() : "unknown";
}
@Override
public int getOrder() {
return -200; // 最优先执行
}
}
/**
* IP 规则配置
*/
@Configuration
public class IpRuleConfig {
@Value("${ip.whitelist:}")
private List<String> whitelist;
@Value("${ip.blacklist:}")
private List<String> blacklist;
public boolean isBlacklisted(String ip) {
return matchesAnyPattern(ip, blacklist);
}
public boolean isWhitelisted(String ip) {
return matchesAnyPattern(ip, whitelist);
}
public boolean hasWhitelist() {
return whitelist != null && !whitelist.isEmpty();
}
private boolean matchesAnyPattern(String ip, List<String> patterns) {
if (patterns == null || patterns.isEmpty()) {
return false;
}
for (String pattern : patterns) {
if (matchesPattern(ip, pattern)) {
return true;
}
}
return false;
}
/**
* 支持 IP 和 CIDR 格式的匹配
* 例如: 192.168.1.1 或 192.168.1.0/24
*/
private boolean matchesPattern(String ip, String pattern) {
if (pattern.contains("/")) {
// CIDR 格式
return IP_ADDRESS_PARSER.isInRange(ip, pattern);
} else {
// 精确匹配
return ip.equals(pattern);
}
}
}
18.5.2 动态规则更新
IP 白名单和黑名单需要支持运行时动态更新,而无需重启服务。我们通过配置中心实现规则的实时推送。
@Component
@Slf4j
public class IpRuleRefresher {
@Autowired
private IpRuleConfig ipRuleConfig;
@ApolloConfigChangeListener
public void onConfigChange(ConfigChangeEvent event) {
if (event.changedKeys().stream().anyMatch(k -> k.startsWith("ip."))) {
log.info("IP 规则配置变更,重新加载");
// 清除缓存,下次访问时重新加载
ipRuleConfig.invalidateCache();
}
}
}
18.6.1 多维度限流策略
网关需要实现多维度的限流策略,包括基于时间窗口的限流、基于令牌桶的限流、基于并发数的限流等。我们采用 Redis + Lua 脚本实现高效的分布式限流。
@Component
@Slf4j
public class RateLimitFilter implements GlobalFilter, Ordered {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private RateLimitConfig rateLimitConfig;
private static final String RATE_LIMIT_PREFIX = "rate:limit:";
/**
* 限流维度:
* 1. 全局限流 - 整个网关的处理能力
* 2. 服务级限流 - 每个微服务的处理能力
* 3. 用户级限流 - 每个用户的请求频率
* 4. IP 级限流 - 每个 IP 的请求频率
*/
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String path = exchange.getRequest().getURI().getPath();
String userId = exchange.getRequest().getHeaders().getFirst("X-User-Id");
String clientIp = getClientIp(exchange.getRequest());
// 获取限流配置
RateLimitConfig.Rule rule = rateLimitConfig.getRule(path);
return Mono.just(rule)
.filter(r -> r != null && r.isEnabled())
.flatMap(r -> checkRateLimit(exchange, chain, userId, clientIp, r))
.otherwiseIfEmpty(chain.filter(exchange));
}
private Mono<Void> checkRateLimit(ServerWebExchange exchange, GatewayFilterChain chain,
String userId, String clientIp, RateLimitConfig.Rule rule) {
String[] keys = buildLimitKeys(userId, clientIp, rule);
List<String> args = Arrays.asList(
String.valueOf(rule.getLimit()),
String.valueOf(rule.getWindowSeconds())
);
// 使用 Lua 脚本执行原子操作
String luaScript = buildRateLimitLuaScript(rule.getAlgorithm());
try {
List<Long> results = redisTemplate.execute(
new DefaultRedisScript<>(luaScript, Long.class),
keys,
args.toArray()
);
long allowed = results != null && !results.isEmpty() ? results.get(0) : 1;
// 添加限流响应头
ServerHttpResponse response = exchange.getResponse();
response.getHeaders().add("X-RateLimit-Limit", String.valueOf(rule.getLimit()));
response.getHeaders().add("X-RateLimit-Remaining", String.valueOf(Math.max(0, allowed - 1)));
if (allowed <= 0) {
log.warn("限流触发: userId={}, clientIp={}, path={}", userId, clientIp, path);
response.getHeaders().add("X-RateLimit-Retry-After", String.valueOf(rule.getWindowSeconds()));
return rateLimitExceeded(exchange, rule);
}
return chain.filter(exchange);
} catch (Exception e) {
log.error("限流检查异常: {}", e.getMessage());
// 限流异常时默认放行,保证服务可用性
return chain.filter(exchange);
}
}
private String[] buildLimitKeys(String userId, String clientIp, RateLimitConfig.Rule rule) {
return new String[] {
RATE_LIMIT_PREFIX + "global",
RATE_LIMIT_PREFIX + "user:" + (userId != null ? userId : "anonymous"),
RATE_LIMIT_PREFIX + "ip:" + clientIp,
RATE_LIMIT_PREFIX + "path:" + rule.getPath()
};
}
private String buildRateLimitLuaScript(String algorithm) {
// 滑动窗口算法
if ("sliding".equals(algorithm)) {
return """
local key = KEYS[1]
local limit = tonumber(ARGV[1])
local window = tonumber(ARGV[2])
local now = tonumber(ARGV[3])
local before = now - window
redis.call('ZREMRANGEBYSCORE', key, 0, before)
local count = redis.call('ZCARD', key)
if count < limit then
redis.call('ZADD', key, now, now .. ':' .. math.random())
redis.call('EXPIRE', key, window)
return 1
else
return 0
end
""";
}
// 令牌桶算法
return """
local key = KEYS[1]
local limit = tonumber(ARGV[1])
local window = tonumber(ARGV[2])
local current = redis.call('GET', key)
if current == false then
current = limit
else
current = tonumber(current)
end
if current > 0 then
redis.call('DECR', key)
return 1
else
return 0
end
""";
}
private Mono<Void> rateLimitExceeded(ServerWebExchange exchange, RateLimitConfig.Rule rule) {
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.TOO_MANY_REQUESTS);
response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
Result<String> result = Result.error(429, "请求过于频繁,请稍后再试");
String body = JsonUtil.toJson(result);
DataBuffer buffer = response.bufferFactory().wrap(body.getBytes());
return response.writeWith(Mono.just(buffer));
}
@Override
public int getOrder() {
return -80;
}
}
18.6.2 熔断降级策略
当后端服务出现故障时,网关需要实现熔断机制,快速失败并返回降级响应,避免请求堆积和级联故障。
@Component
@Slf4j
public class CircuitBreakerFilter implements GlobalFilter, Ordered {
@Autowired
private CircuitBreakerRegistry circuitBreakerRegistry;
@Autowired
private FallbackHandlerRegistry fallbackHandlerRegistry;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String routeId = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
if (routeId == null) {
return chain.filter(exchange);
}
CircuitBreaker circuitBreaker = circuitBreakerRegistry.circuitBreaker(routeId);
FallbackHandler fallbackHandler = fallbackHandlerRegistry.getFallbackHandler(routeId);
// 使用 transformDeferred 实现延迟绑定
return chain.filter(exchange, DecoratorFunction.configuration(circuitBreaker))
.transformDeferred(
operatorType == OperatorType.ERROR ? Mono.error(e) : Mono.just(exchange)
)
.onErrorResume(Throwable.class, e -> {
log.error("调用服务 {} 失败: {}", routeId, e.getMessage());
// 触发熔断
circuitBreaker.accept(e);
// 返回降级响应
if (fallbackHandler != null) {
return fallbackHandler.handle(exchange, e);
}
return buildDefaultFallback(exchange, routeId);
});
}
private Mono<Void> buildDefaultFallback(ServerWebExchange exchange, String routeId) {
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.SERVICE_UNAVAILABLE);
response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
response.getHeaders().add("X-CircuitBreaker", "open");
Result<String> result = Result.error(503, "服务暂时不可用,请稍后再试");
String body = JsonUtil.toJson(result);
DataBuffer buffer = response.bufferFactory().wrap(body.getBytes());
return response.writeWith(Mono.just(buffer));
}
@Override
public int getOrder() {
return -70;
}
}
18.7.1 AI 服务调用鉴权
AI 大模型服务通常需要额外的安全防护,因为调用成本较高,且涉及敏感数据。我们需要实现专门的 AI 服务鉴权机制。
@Component
@Slf4j
public class AiServiceAuthFilter implements GlobalFilter, Ordered {
@Autowired
private AiQuotaService aiQuotaService;
@Autowired
private ContentFilterService contentFilterService;
@Value("${ai.service.enabled:true}")
private boolean aiServiceEnabled;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String path = exchange.getRequest().getURI().getPath();
// 只处理 AI 相关接口
if (!path.startsWith("/api/ai/")) {
return chain.filter(exchange);
}
// 检查 AI 服务是否启用
if (!aiServiceEnabled) {
return serviceUnavailable(exchange, "AI 服务已关闭");
}
// 获取用户信息
String userId = exchange.getRequest().getHeaders().getFirst("X-User-Id");
// 检查用户 AI 配额
return aiQuotaService.checkQuota(userId)
.flatMap(quota -> {
if (!quota.hasRemaining()) {
log.warn("用户 {} AI 配额已用尽", userId);
return quotaExceeded(exchange, quota);
}
// 检查敏感内容
String requestBody = extractRequestBody(exchange);
return contentFilterService.checkContent(requestBody)
.flatMap(filterResult -> {
if (filterResult.isBlocked()) {
log.warn("用户 {} 请求内容被拦截: {}", userId, filterResult.getReason());
return contentBlocked(exchange, filterResult.getReason());
}
// 添加配额相关头
ServerHttpResponse response = exchange.getResponse();
response.getHeaders().add("X-AI-Quota-Remaining", String.valueOf(quota.getRemaining()));
response.getHeaders().add("X-AI-Quota-Limit", String.valueOf(quota.getLimit()));
return chain.filter(exchange);
});
})
.onErrorResume(e -> {
log.error("AI 服务鉴权异常: {}", e.getMessage());
return buildDefaultFallback(exchange);
});
}
private Mono<Void> quotaExceeded(ServerWebExchange exchange, AiQuota quota) {
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.PAYMENT_REQUIRED);
response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
response.getHeaders().add("X-AI-Quota-Reset", String.valueOf(quota.getResetTime()));
Result<String> result = Result.error(402, "AI 配额已用尽,请升级套餐或等待配额重置");
String body = JsonUtil.toJson(result);
DataBuffer buffer = response.bufferFactory().wrap(body.getBytes());
return response.writeWith(Mono.just(buffer));
}
@Override
public int getOrder() {
return -60;
}
}
本章节我们详细介绍了微服务架构中网关统一鉴权的设计与实现。通过 Spring Cloud Gateway 的全局过滤器,我们实现了 JWT 令牌处理、双令牌刷新机制、基于角色的权限控制、IP 黑白名单管理、多维度限流熔断等核心功能。在大模型服务场景下,我们还实现了专门的 AI 服务鉴权机制,包括配额管理和内容安全过滤。
下一章节中,我们将介绍多租户隔离方案,探讨如何在微服务架构中实现租户数据的隔离和资源的合理分配。

697

被折叠的 条评论
为什么被折叠?



