Java 程序员第 43 阶段 18:微服务整合大模型,网关统一鉴权设计实战

在微服务架构中,网关是所有请求的入口,承担着请求路由、负载均衡、限流熔断、统一鉴权等核心功能。本章节将深入探讨如何在网关层实现统一的安全认证和授权管理,涵盖 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 服务鉴权机制,包括配额管理和内容安全过滤。

下一章节中,我们将介绍多租户隔离方案,探讨如何在微服务架构中实现租户数据的隔离和资源的合理分配。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

洛水石

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值