🌐 从零构建高可用 API 网关:鉴权、路由、性能优化全解析

🌐 从零构建高可用 API 网关:鉴权、路由、性能优化全解析

作者 :古渡蓝按
技术栈 :Spring Cloud Gateway + redis + Nacos + 自定义鉴权
技术栈 :微信公众号(深入浅出谈java)

感觉本篇对你有帮助可以关注一下,会不定期更新知识和面试资料、技巧!!!


一、引言:为什么我们需要 API 网关?

在微服务架构中,随着服务数量的增加,直接暴露后端服务给客户端会带来诸多问题:

  • 安全隐患:每个服务都要重复实现鉴权逻辑
  • 路径混乱:客户端需要维护多个服务地址
  • 重复代码:日志、监控、限流等横切关注点重复开发
  • 升级困难:服务变更对客户端影响大

API 网关(API Gateway) 应运而生,它作为系统的统一入口,承担了 路由转发、安全控制、协议转换、流量治理 等核心职责。

本文将带你从零开始,搭建一个 支持动态路由、全局鉴权、路径重写、高并发 的生产级网关服务,并深入剖析其设计思路、调用链路与性能优化策略。


二、网关的核心作用与典型场景

1. 网关的五大核心能力

能力 说明
✅ 路由转发 将请求按规则转发到对应微服务
✅ 协议转换 HTTP → gRPC、WebSocket 等
✅ 安全控制 鉴权、防重放、防篡改
✅ 流量治理 限流、熔断、降级
✅ 监控审计 请求日志、调用链追踪、QPS 监控

2. 典型应用场景

  • 统一入口 :所有请求走 /api/** 统一入口
  • 安全加固:防止未授权访问、签名验证、防刷
  • 灰度发布:根据 Header 路由到不同版本服务
  • 前后端分离:解决跨域、路径代理
  • API 聚合:合并多个接口返回(BFF 模式)

三、技术选型:为什么选择 Spring Cloud Gateway?

对比项 Nginx Zuul 1.x Spring Cloud Gateway
性能 ⭐⭐⭐⭐⭐ ⭐⭐ ⭐⭐⭐⭐
编程模型 配置化 同步阻塞 异步非阻塞(Netty)
动态路由 需 reload 支持 支持(可对接 Nacos)
扩展性 有限 一般 高(Filter 机制)
开发成本 中(Java 编写)
适用场景 静态代理 旧项目 微服务网关首选

结论:SCG 基于 Reactor 和 Netty,性能高、扩展性强,是微服务网关的理想选择。


四、架构设计:整体架构与模块划分

graph TD AClient --> BAPI Gateway B --> CRoute: /pass/\*\* B --> DRoute: /auth/\*\* B --> ERoute: /file/\*\* C --> FAuthGlobalFilter F --> G鉴权逻辑 G --> H{鉴权成功?} H -->|是| IStripPrefix → 转发 H -->|否| J返回 401 I --> KService A I --> LService B

核心模块

  1. 动态路由模块 :基于 RouteLocator 实现路径匹配
  2. 全局过滤器GlobalFilter 实现鉴权、日志等
  3. 安全模块:自定义签名验证(HMAC-SHA256)
  4. 性能优化:GZIP、缓存、JVM 调优

五、核心实现:全局鉴权过滤器深度解析

1. 需求分析

  • ✅ 支持 POST 请求体参与签名
  • ✅ 支持 GET 请求参数透传
  • ✅ 防重放攻击(timestamp + nonce)
  • ✅ 签名验证失败返回 401

2. 关键代码:AuthGlobalFilter

java 复制代码
@Component
public class AuthGlobalFilter implements GlobalFilter, Ordered {

    private final Map<String, String> appSecrets = Map.of("APP_A_001", "your-secret-key-123");

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        String path = request.getURI().getPath();

        // 仅拦截 /pass/** 请求
        if (!path.startsWith("/pass/")) {
            return chain.filter(exchange);
        }

        // 读取并缓存请求体(关键!保证 body 可重复读)
        return DataBufferUtils.join(request.getBody())
                .flatMap(dataBuffer -> {
                    byte[] bodyBytes = new byte[dataBuffer.readableByteCount()];
                    dataBuffer.read(bodyBytes);
                    DataBufferUtils.release(dataBuffer);

                    String body = new String(bodyBytes, StandardCharsets.UTF_8);
                    exchange.getAttributes().put("cachedRequestBody", body);

                    // 重新包装 request,保证 body 能转发到下游
                    Flux<DataBuffer> cachedBodyFlux = Flux.defer(() -> 
                        Mono.just(exchange.getResponse().bufferFactory().wrap(bodyBytes))
                    );

                    ServerHttpRequest mutatedRequest = new ServerHttpRequestDecorator(request) {
                        @Override
                        public Flux<DataBuffer> getBody() {
                            return cachedBodyFlux;
                        }
                    };

                    ServerWebExchange newExchange = exchange.mutate().request(mutatedRequest).build();

                    // 执行鉴权
                    return performAuthentication(newExchange, body)
                            .then(Mono.defer(() -> chain.filter(newExchange)))
                            .onErrorResume(ex -> {
                                if (ex instanceof AuthenticationException) {
                                    return onError(exchange.getResponse(), ex.getMessage(), HttpStatus.UNAUTHORIZED);
                                }
                                return onError(exchange.getResponse(), "Internal error", HttpStatus.INTERNAL_SERVER_ERROR);
                            });
                })
                .switchIfEmpty(chain.filter(exchange)); // GET 请求直接放行
    }

    private Mono<Void> performAuthentication(ServerWebExchange exchange, String body) {
        ServerHttpRequest request = exchange.getRequest();

        String appId = request.getHeaders().getFirst("X-Pass-AppId");
        String timestampStr = request.getHeaders().getFirst("X-Pass-Timestamp");
        String nonce = request.getHeaders().getFirst("X-Pass-Nonce");
        String sign = request.getHeaders().getFirst("X-Pass-Sign");

        // 省略参数校验...

        String secret = appSecrets.get(appId);
        if (secret == null) {
            return onError(exchange.getResponse(), "Invalid AppId", HttpStatus.UNAUTHORIZED);
        }

        // 验证时间戳(5分钟内)
        try {
            long timestamp = Long.parseLong(timestampStr);
            long now = Instant.now().getEpochSecond();
            if (Math.abs(now - timestamp) > 300) {
                return onError(exchange.getResponse(), "Timestamp expired", HttpStatus.UNAUTHORIZED);
            }
        } catch (NumberFormatException e) {
            return onError(exchange.getResponse(), "Invalid timestamp", HttpStatus.UNAUTHORIZED);
        }

        // ✅ 使用 path + body + timestamp + nonce 生成签名
        String contentToSign = request.getURI().getPath() + body + timestampStr + nonce;
        String expectedSign = SignUtil.sign(contentToSign, secret);

        if (!expectedSign.equalsIgnoreCase(sign)) {
            return onError(exchange.getResponse(), "Invalid signature", HttpStatus.UNAUTHORIZED);
        }

        return Mono.empty();
    }

    private Mono<Void> onError(ServerHttpResponse response, String msg, HttpStatus status) {
        response.setStatusCode(status);
        response.getHeaders().add("Content-Type", "application/json");
        String body = String.format("{\"code\": %d, \"message\": \"%s\"}", status.value(), msg);
        return response.writeWith(Mono.just(response.bufferFactory().wrap(body.getBytes(StandardCharsets.UTF_8))));
    }

    @Override
    public int getOrder() {
        return -1; // 优先执行
    }
}

3. 关键设计点

问题 解决方案
POST 请求体只能读一次 DataBufferUtils.join() 缓存 body
❓ 缓存后如何转发到下游 ServerHttpRequestDecorator 包装 request
GET 请求如何处理 switchIfEmpty(chain.filter(exchange)) 直接放行
❓ 鉴权失败如何中断 onErrorResume 返回错误响应

六、调用链路:一次请求的完整旅程

POST /pass/test/create 为例:

详细请求路径分析

  1. 请求进入网关
  2. RoutePredicateHandlerMapping:匹配路由
  3. FilteringWebHandler:执行 GlobalFilter 和 GatewayFilter
  4. 路由转发到目标地址(如 http://www.baidu.com/order/16)

我们一步步来看:

🔹 步骤 1:路由匹配(Route Matching)

  • 网关会从所有可用的 RouteDefinition 中查找匹配的路由。

  • 你的两个路由源都会被加载:

  • 来自 CustomRouteConfigPath=/pass/order/**http://www.baidu.com

    • 来自 application.ymlPath=/pass/**http://localhost:8080
  • 由于 /pass/order/** 是更具体的路径,它会优先匹配(前提是 order 设置合理)。

  • 匹配成功后,生成一个Route对象,包含:

    复制代码
    ID: `order_route`
    URI: `http://www.baidu.com`
    Predicates: `Path=/pass/order/**`
    Filters: (如果有)

🔹 步骤 2:执行 GlobalFilter(AuthGlobalFilter)

  • 匹配路由后,网关进入过滤器链。

  • AuthGlobalFilterGlobalFilter,它的 filter() 方法会被调用

  • ✅ 此时,请求路径 /pass/order/16 被用于路由匹配 ,但 CustomRouteConfig 本身 不会主动收到这个请求信息


java 复制代码
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
    ServerHttpRequest request = exchange.getRequest();
    String path = request.getURI().getPath(); // → "/pass/order/16"
    ...
}

✅ 在这里,你可以获取到完整的请求信息,包括:

  • 路径:/pass/order/16
  • Header:X-Pass-AppId, X-Pass-Sign
  • 方法:GET/POST
  • 查询参数等

👉 这是你做鉴权的正确位置。


🔹 步骤 3:路由转发

  • 鉴权通过后,chain.filter(exchange) 继续执行。

  • 网关根据 Route 中的 urihttp://www.baidu.com)和 predicates 进行转发。

  • 最终请求被转发为:

    复制代码
    GET http://www.baidu.com/order/16

七、请求调试

网关日志打印日志

下游系统

八、总结

本文从零搭建了一个生产级 API 网关,实现了:

  • ✅ 基于路径的动态路由
  • ✅ 支持 Body 参与签名的全局鉴权
  • ✅ GET/POST 请求兼容处理
  • ✅ 高并发性能优化

网关不是简单的"转发器",而是微服务架构的"安全门"与"流量调度中心"

通过合理设计与优化,即使是 2核4G 的机器,也能扛住上千 QPS,为业务保驾护航。

相关推荐
米丘3 天前
微前端之 Web Components 完全指南
微服务·html
霸道流氓气质6 天前
领域驱动设计(DDD)在 Spring Boot 微服务中的实践指南
运维·spring boot·微服务
霸道流氓气质6 天前
Spring Boot 微服务性能优化完全指南
spring boot·微服务·性能优化
地瓜伯伯6 天前
从MESI缓存一致性协议讲透synchronized的底层
java·spring boot·spring·spring cloud·微服务·springcloud
Devin~Y6 天前
大厂 Java 面试实录:从音视频内容社区到 AI RAG 的全链路技术设计
java·spring boot·redis·spring cloud·微服务·kafka·音视频
递归尽头是星辰6 天前
AI 访问数据仓库:从直连到微服务化
数据仓库·人工智能·微服务·dataagent·ai数据治理
就改了6 天前
Windows 环境 SkyWalking 完整实操教程
windows·微服务·skywalking
至乐活着7 天前
Docker Compose多服务编排实战:从零搭建Node.js+MySQL+Redis全栈应用
docker·微服务·devops·容器编排·compose
就改了7 天前
微服务异步场景链路断裂完整解决方案
微服务·云原生·架构
山东点狮信息科技有限公司7 天前
点狮OA-企业级 OA 办公自动化系统架构设计与实践
spring cloud·微服务·性能优化·架构·系统架构