API 网关安全配置

在微服务架构中,API 网关是所有流量的唯一入口。它就像一座城堡的大门——所有的进出都要经过这道关卡。如果大门失守,整个城堡都会暴露在攻击者面前。

API 网关的安全配置决定了整个 API 体系的安全基线。一个配置良好的网关可以挡住 90% 的攻击,而一个配置不当的网关则可能成为最大的安全漏洞。

API 网关的安全职责

API 网关承担着多层安全职责:

flowchart TD
    subgraph 网关层
        R1[路由安全]
        R2[TLS 终止]
        R3[协议转换]
    end
    
    subgraph 认证授权层
        A1[身份认证]
        A2[Token 验证]
        A3[权限检查]
    end
    
    subgraph 流量控制层
        F1[限流]
        F2[熔断]
        F3[黑名单]
    end
    
    subgraph 防护层
        P1[WAF 集成]
        P2[注入防护]
        P3[爬虫识别]
    end
    
    subgraph 监控层
        M1[日志记录]
        M2[指标采集]
        M3[告警通知]
    end
    
    R1 --> A1 --> F1 --> P1 --> M1

网关层的核心安全功能

功能说明优先级
TLS 终止统一处理 HTTPS,减少后端服务压力必须
证书管理集中管理证书,支持自动续期必须
请求验证基础格式校验、必填参数检查必须
认证JWT/API Key/OAuth2 Token 验证必须
限流防止 DoS 和资源滥用必须
CORS跨域请求控制必须
WAF 集成阻断 Web 攻击建议
熔断保护后端服务建议

网关层的认证配置

JWT Token 验证

kong-jwt-plugin.yaml
# Kong JWT 插件配置
plugins:
  - name: jwt
    config:
      # 密钥来源配置
      key_claim_name: "iss"
      
      # 签名验证
      maximum_expiration: 3600  # 最大过期时间(秒)
      
      # 算法白名单(安全配置)
      supported_algorithms:
        - RS256
        - RS384
        - RS512
        # 不允许 HS256(除非明确需要)
      
      # Token 位置
      token_prefix: "Bearer "
      
      # URI 参数名称(备用)
      uri_param_names:
        - jwt
      
      # Claims 验证
      claims_to_verify:
        - exp      # 必须验证过期时间
        - nbf      # 验证生效时间
      # 不验证 iat(发行时间),避免时钟偏差问题

      # 秘密密钥(对于 HS256)
      # 如果使用 HS256,需要配置 secret
      # secret_is_base64: false
apisix-jwt-plugin.yaml
# APISIX JWT Auth 插件配置
plugins:
  jwt-auth:
    - secret_key: your-secret-key  # 用于 HS256
      key: user-key                # 客户端使用的 key
      
      # 推荐使用 RS256,使用公钥
      # public_key: |
      #   -----BEGIN PUBLIC KEY-----
      #   ...
      #   -----END PUBLIC KEY-----
      
      algorithm: RS256

# 路由配置
routes:
  - uri: /api/*
    plugins:
      jwt-auth:
        key: user-key
        algorithm: RS256
        public_key: /path/to/public.pem

API Key 验证

kong-api-key-plugin.yaml
plugins:
  - name: key-auth
    config:
      # Key 的位置
      key_in:
        - header
        - query
        # 不允许在 body 中传递(安全考虑)
      
      # Header 名称
      header_names:
        - X-API-Key
        - Authorization  # 也支持 Basic Auth
      
      # 自动创建 Key(谨慎使用)
      # 自动创建功能应该关闭
      hide_credentials: true  # 隐藏原始凭证,不传递给后端
nginx-api-key.conf
# Nginx + Lua 实现 API Key 验证
server {
    listen 443 ssl;
    
    location /api/ {
        # 读取 API Key
        set $api_key $http_x_api_key;
        
        # 验证 API Key
        access_by_lua_block {
            local redis = require "resty.redis"
            local red = redis:new()
            
            -- 连接 Redis
            local ok, err = red:connect("127.0.0.1", 6379)
            if not ok then
                ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
            end
            
            -- 查询 Key 是否有效
            local key_hash = ngx.encode_base64(
                ngx.sha1_bin(api_key))
            local exists, err = red:sismember(
                "apikeys:valid", key_hash)
            
            if not exists then
                ngx.exit(ngx.HTTP_UNAUTHORIZED)
            end
            
            -- 检查 Key 的作用域
            local scope = red:hget(
                "apikeys:scope", key_hash)
            
            -- 验证访问路径是否在范围内
            if not validate_scope(scope, ngx.var.uri) then
                ngx.exit(ngx.HTTP_FORBIDDEN)
            end
            
            red:close()
        }
        
        proxy_pass http://backend;
    }
}

网关层的授权配置

基于路由的权限控制

kong-acl-plugin.yaml
plugins:
  - name: acl
    config:
      # 允许访问的消费者组
      allow:
        - admin-group
        - user-group
      
      # 拒绝访问的消费者组
      deny:
        - blocked-group
      
      # 隐藏未授权的 upstream 配置
      hide_groups_header: false  # 不隐藏,方便调试
apisix-authz-rbac-plugin.yaml
plugins:
  - name: authz-rbac
    config:
      # 权限规则
      rules:
        - method: GET
          path: /api/admin/*
          role: admin
        - method: GET
          path: /api/user/*
          role: user
        - method: *
          path: /api/public/*
          role: anonymous
      
      # 拒绝时的响应
      unauthorized_response:
        error_code: 403
        message: "Access denied"

# 使用 openid-connect 插件实现更复杂的授权
routes:
  - uri: /api/*
    plugins:
      openid-connect:
        auth_methods:
          - userinfo
          - introspection
        token_signing_alg_values_accepted:
          - RS256
        scopes:
          - openid
          - profile
          - email
        jwt-secret:
          - from_headers:
              name: Authorization
              value_prefix: "Bearer "

参数级授权

ParameterAuthorizationFilter.java
@Component
public class ParameterAuthorizationFilter extends ZuulFilter {
    
    @Autowired
    private AuthorizationService authorizationService;
    
    @Override
    public String filterType() {
        return "pre";
    }
    
    @Override
    public int filterOrder() {
        return 10;
    }
    
    @Override
    public boolean shouldFilter() {
        return requiresAuthorization();
    }
    
    @Override
    public Object run() {
        RequestContext ctx = RequestContext.getCurrentContext();
        HttpServletRequest request = ctx.getRequest();
        
        // 获取当前用户
        String userId = getCurrentUserId();
        String userRole = getCurrentUserRole();
        
        // 校验用户与资源的关系
        String resourceType = extractResourceType(request.getRequestURI());
        String resourceId = extractResourceId(request);
        
        if (!authorizationService.canAccess(userId, userRole, 
                resourceType, resourceId, request.getMethod())) {
            ctx.setSendZuulResponse(false);
            ctx.setResponseStatusCode(403);
            ctx.setResponseBody("{\"error\":\"Access denied\"}");
            return null;
        }
        
        return null;
    }
}

网关层的流量控制

请求限流配置

kong-rate-limiting-plugin.yaml
plugins:
  - name: rate-limiting
    config:
      # 策略:local(单节点)/redis(集群)/cluster(Kong 集群)
      policy: redis
      
      # 限流维度
      fault_tolerant: true  # Redis 故障时是否允许请求通过
      
      # 每分钟限制
      minute: 60
      
      # 每小时限制
      hour: 500
      
      # 每天限制
      day: 10000
      
      # 限制错误时的响应
      limit: 100
      error_code: 429
      error_message: "Rate limit exceeded"
      
      # 添加限流相关的响应头
      hide_client_header: false
      
      # 同步到 Redis 的间隔
      sync_rate: -1  # 实时同步
      
      # 视频:大客户可以配置更高的限制
      # 使用消费者(Consumer)配置不同限额
apisix-rate-limit-plugin.yaml
plugins:
  rate-limiting:
    - plugins:
        rate-limiting:
          max: 100
          min: 1
          time_window: 60  # 60 秒
          policy: redis
          
          # Redis 配置
          redis_host: 127.0.0.1
          redis_port: 6379
          redis_password: ""
          redis_database: 0
          
          # 超过限制时的响应
          key: remote_addr  # 按 IP 限流
          key_type: var
          
          # 是否在响应头中包含限流信息
          show_limit_quota_in_header: true

熔断配置

kong-circuit-breaker.yaml
plugins:
  - name: circuit-breaker
    config:
      # 熔断触发条件
      response_body: "Service unavailable"
      healthy_threshold: 3  # 连续 3 次成功则恢复
      unhealthy_threshold: 3  # 连续 3 次失败则熔断
      
      # 超时配置
      request_timeout: 3000  # 3 秒
      connect_timeout: 1000  # 1 秒
      
      # HTTP 状态码判断
      http_statuses:
        - 500
        - 503
        - 504

请求/响应转换与安全

请求转换

kong-request-transformer.yaml
plugins:
  - name: request-transformer
    config:
      # 添加安全相关的 Header
      add:
        headers:
          - "X-Request-ID:$request_id"
          - "X-Forwarded-For:$remote_addr"
      
      # 删除敏感 Header
      remove:
        headers:
          - "X-Internal-Secret"
          - "X-Debug-Token"
      
      # 重写请求参数
      replace:
        args:
          - "token:null"  # 替换空 token
      
      # 添加请求 ID(用于追踪)
      rename:
        headers:
          X-Request-Id: X-Correlation-ID

响应转换

kong-response-transformer.yaml
plugins:
  - name: response-transformer
    config:
      # 删除敏感响应头
      remove:
        headers:
          - "Server"
          - "X-Powered-By"
          - "X-AspNet-Version"
      
      # 修改响应头
      replace:
        headers:
          - "Strict-Transport-Security:max-age=31536000; includeSubDomains"
          - "X-Content-Type-Options:nosniff"
      
      # 删除敏感响应体字段
      remove:
        json:
          - "password"
          - "secret"
          - "token"
          - "creditCard"

协议转换中的安全问题

当 API 网关进行协议转换(如 HTTP → gRPC)时,需要注意额外的安全问题:

HTTP → gRPC 转换

grpc-transcoding.yaml
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: product-service
spec:
  hosts:
    - product-service
  http:
    - match:
        - uri:
            regex: "/api/v1/products/.*"
      route:
        - destination:
            host: product-service-grpc
            port:
              number: 50051
      route:
        - destination:
            host: product-service
            port:
              number: 8080
      # gRPC-JSON 转码配置
      cors:
        allowOrigins:
          - "https://example.com"
        allowMethods:
          - GET
          - POST
          - OPTIONS
        allowHeaders:
          - Authorization
          - Content-Type
        maxAge: "86400"

安全考虑

ProtocolConversionSecurityFilter.java
public class ProtocolConversionSecurityFilter implements Filter {
    
    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
                        FilterChain chain) throws IOException, ServletException {
        
        // 1. 验证协议转换的安全性
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        
        // 检查 Content-Type
        String contentType = httpRequest.getContentType();
        if (!isAllowedContentType(contentType)) {
            sendError(response, 415, "Unsupported Media Type");
            return;
        }
        
        // 2. 验证字符编码
        String encoding = httpRequest.getCharacterEncoding();
        if (encoding == null || !encoding.equalsIgnoreCase("UTF-8")) {
            // 设置默认编码,防止编码攻击
            httpRequest.setCharacterEncoding("UTF-8");
        }
        
        // 3. 验证请求大小
        long contentLength = httpRequest.getContentLengthLong();
        if (contentLength > MAX_REQUEST_SIZE) {
            sendError(response, 413, "Payload Too Large");
            return;
        }
        
        // 4. 清理危险的头部
        cleanDangerousHeaders(httpRequest);
        
        chain.doFilter(request, response);
    }
    
    private void cleanDangerousHeaders(HttpServletRequest request) {
        // 禁止的前缀(防止注入)
        String[] forbiddenPrefixes = {
            "X-Internal-",
            "X-System-",
            "X-Admin-"
        };
        
        // 在网关层验证,不信任来自客户端的这些头部
    }
}

API 网关的 TLS 配置

Kong TLS 配置

kong-tls-config.yaml
# Kong SSL 配置
format_version: "3.0"

_stream: []

services:
  - name: api-backend
    url: http://backend:8080
    routes:
      - name: api-route
        protocols:
          - https
        paths:
          - /api/
        strip_path: true
        
        # HTTPS 配置
        https_redirect_status_code: 426
        
        # 证书配置
        snis:
          - name: api.example.com
            certificate: /etc/kong/ssl/cert.pem
            key: /etc/kong/ssl/key.pem

certificates:
  - id: cert-1
    cert: |
      -----BEGIN CERTIFICATE-----
      ...
      -----END CERTIFICATE-----
    key: |
      -----BEGIN PRIVATE KEY-----
      ...
      -----END PRIVATE KEY-----
    
    # TLS 版本控制
    ssl_cert_default: |
      # 中间证书
    tags:
      - example.com

APISIX TLS 配置

apisix-tls-config.yaml
routes:
  - uri: /api/*
    plugins:
      # HTTPS 重定向
      redirect:
        http_to_https: true
    
    upstream:
      type: roundrobin
      nodes:
        - host: backend
          port: 8080
          weight: 100

# SSL 证书配置
ssls:
  - id: ssl-1
    sni: api.example.com
    cert: |
      -----BEGIN CERTIFICATE-----
      ...
      -----END CERTIFICATE-----
    key: |
      -----BEGIN PRIVATE KEY-----
      ...
      -----END PRIVATE KEY-----
    
    # TLS 版本和加密套件
    ssl_protocols:
      - TLSv1.2
      - TLSv1.3
    
    ssl_ciphers: |
      ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:
      ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384

Nginx TLS 配置

nginx-tls.conf
server {
    listen 443 ssl http2;
    server_name api.example.com;
    
    # 证书配置
    ssl_certificate /etc/nginx/ssl/fullchain.pem;
    ssl_certificate_key /etc/nginx/ssl/privkey.pem;
    
    # TLS 版本配置(禁用不安全的版本)
    ssl_protocols TLSv1.2 TLSv1.3;
    
    # 安全的加密套件
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:
                 ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:
                 ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305;
    
    ssl_prefer_server_ciphers off;
    
    # 安全头
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
    add_header X-Frame-Options DENY always;
    add_header X-Content-Type-Options nosniff always;
    add_header X-XSS-Protection "1; mode=block" always;
    
    # OCSP Stapling
    ssl_stapling on;
    ssl_stapling_verify on;
    resolver 8.8.8.8 8.8.4.4 valid=300s;
    
    # 会话复用
    ssl_session_timeout 1d;
    ssl_session_cache shared:SSL:50m;
    ssl_session_tickets off;
    
    location / {
        proxy_pass http://backend;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

常见 API 网关安全配置对比

功能KongAPISIXNginx
认证插件JWT, Key Auth, OAuth2, LDAPJWT Auth, Key Auth, Authz需要 Lua 脚本
限流内置插件内置插件ngx_http_limit_req_module
熔断Circuit Breaker 插件Circuit Breaker 插件需要模块组合
WAF需集成第三方需集成ModSecurity
配置方式Declarative YAMLDeclarative YAMLNGINX conf
扩展性Lua/Go 插件Lua/WASM 插件Lua 模块
性能极高极高
学习曲线中等中等陡峭

安全配置推荐

场景推荐方案理由
快速起步Kong丰富的插件生态,开箱即用
高性能要求APISIX基于 Apache APISIX,性能优异
简单场景Nginx配置简单,资源占用低
企业级Kong + WAFKong 插件丰富 + 专业 WAF

微服务架构下的网关策略

统一网关 vs 边界网关

flowchart TD
    subgraph 统一网关["统一网关架构"]
        G1[API Gateway]
        G1 --> S1[Service A]
        G1 --> S2[Service B]
        G1 --> S3[Service C]
    end
    
    subgraph 边界网关["边界网关架构"]
        G2[External Gateway]
        G3[Internal Gateway]
        G2 --> S1
        G2 --> S2
        G3 --> S2
        G3 --> S3
    end

统一网关:所有流量(内外)都经过同一个网关。优点是管理简单,缺点是安全策略可能过于严格,影响内部服务间调用效率。

边界网关:外部流量经过外部网关,内部流量经过内部网关或直接通信。优点是内外策略可以差异化,缺点是管理复杂。

推荐架构

istio-gateway.yaml
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: external-gateway
  namespace: istio-system
spec:
  selector:
    istio: ingressgateway
  servers:
    - port:
        number: 443
        name: https
        protocol: HTTPS
      tls:
        mode: SIMPLE
        credentialName: api-cert
      hosts:
        - "api.example.com"
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: api-virtual-service
spec:
  hosts:
    - "api.example.com"
  gateways:
    - external-gateway
  http:
    - match:
        - uri:
            prefix: /api/v1
      route:
        - destination:
            host: api-service
            port:
              number: 8080
          weight: 90
        - destination:
            host: api-service-canary
            port:
              number: 8080
          weight: 10
      # 流量治理
      retries:
        attempts: 3
        perTryTimeout: 2s
        retryOn: gateway-error,connect-failure,refused-stream
      timeout: 10s
      # CORS 配置
      corsPolicy:
        allowOrigins:
          - exact: "https://example.com"
        allowMethods:
          - GET
          - POST
          - PUT
          - DELETE
          - OPTIONS
        allowHeaders:
          - Authorization
          - Content-Type
        exposeHeaders:
          - X-Request-ID
        maxAge: 86400s

思考题

问题 1:API 网关的认证和后端服务的认证应该如何分工?哪些应该在网关层做,哪些应该在后端做?

参考答案

认证分层原则

网关层(必须做)

  1. TLS 终止:所有请求在网关层解密
  2. 基础 Token 验证:验证 Token 格式、签名、有效期
  3. IP 黑名单/白名单:网络层访问控制
  4. 限流:防止 DoS
  5. 日志记录:记录原始请求信息

网关层(可选做)

  1. 完整权限检查:如果权限逻辑简单且统一
  2. 敏感数据过滤:响应体过滤

后端服务(必须做)

  1. 业务权限检查:用户与资源的所有权关系
  2. 细粒度授权:字段级、参数级权限
  3. 审计日志:业务操作的详细日志

后端服务(推荐做)

  1. 参数验证:业务逻辑相关的参数校验
  2. 数据脱敏:根据用户角色返回不同字段

实际建议

网关层:验证 Token 有效 + 用户身份(userId)
后端层:验证用户是否有权访问这个具体资源

这样分层的好处是:

  • 网关层性能高,适合大量请求的快速过滤
  • 后端层灵活性高,可以根据业务做细粒度控制

问题 2:如何评估 API 网关的性能开销,并确定是否需要做性能优化?

参考答案

性能开销评估

1. 基准测试

# 使用 wrk 进行基准测试
wrk -t12 -c400 -d30s https://api.example.com/api/users

# 测试结果分析
# - Latency: 平均延迟和 P99 延迟
# - QPS: 每秒请求数
# - Error Rate: 错误率

2. 关键指标

指标可接受需要优化
网关延迟增加< 5ms> 10ms
CPU 使用率< 70%> 85%
内存使用稳定持续增长
错误率< 0.1%> 1%

3. 优化策略

插件优化

  • 禁用不必要的插件
  • 插件执行顺序优化
  • 热点插件本地缓存

配置优化

  • 长连接复用
  • 连接池配置
  • DNS 缓存

架构优化

  • 网关水平扩展
  • 就近接入(边缘节点)
  • 异步日志
# 优化示例:禁用不必要的插件
plugins:
  # 只启用必要的插件
  - name: jwt
  - name: rate-limiting
  - name: proxy-cache
    config:
      response_code:
        - 200
      request_method:
        - GET
      content_type:
        - "application/json"

何时优化

  1. 网关延迟 > 后端延迟 20%
  2. 网关成为瓶颈(CPU 打满)
  3. 吞吐量不满足需求

问题 3:在多云或混合云环境下,如何统一管理多个 API 网关的安全配置?

参考答案

多云 API 网关管理挑战

  1. 配置一致性:不同云平台的 API 网关配置语法不同
  2. 证书管理:多云环境证书分发复杂
  3. 密钥管理:跨云密钥同步困难
  4. 监控告警:需要统一视图

解决方案:Infrastructure as Code + GitOps

1. 统一的配置抽象

unified-gateway-config.yaml
# 平台无关的配置格式
gateway:
  name: api-gateway
  environments:
    - name: production
      cloud: aws
      region: ap-east-1
    - name: production
      cloud: azure
      region: eastus
  
  security:
    tls:
      min_version: "1.2"
      cert_manager: cert-manager
    auth:
      jwt:
        issuer: "https://auth.example.com"
        algorithms: ["RS256"]
    rate_limit:
      global: 10000
      per_user: 100

2. 使用 Crossplane 或 Terraform 管理

crossplane-provider.yaml
apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
  name: gateway-config
spec:
  compositeTypeRef:
    apiVersion: example.com/v1
    kind: GatewayConfig
  resources:
    - base:
        apiVersion: konghq.com/v1
        kind: KongConsumer
      patches:
        - fromFieldPath: spec.username
          toFieldPath: spec.username

3. 统一密钥管理

使用 Vault 或 AWS Secrets Manager 统一管理密钥:

public class UnifiedSecretManager {
    
    private final Map<String, SecretClient> clients = Map.of(
        "aws", new AwsSecretsManagerClient(),
        "azure", new AzureKeyVaultClient(),
        "gcp", new GcpSecretManagerClient()
    );
    
    public String getSecret(String cloud, String secretName) {
        SecretClient client = clients.get(cloud);
        return client.getSecret(secretName);
    }
    
    // 跨云密钥同步
    public void syncSecret(String secretName, Map<String, byte[]> values) {
        for (Map.Entry<String, byte[]> entry : values.entrySet()) {
            String cloud = entry.getKey();
            byte[] value = entry.getValue();
            clients.get(cloud).setSecret(secretName, value);
        }
    }
}

4. 统一监控

使用 OpenTelemetry 统一采集指标:

telemetry:
  exporters:
    - otlp:
        endpoint: otel-collector:4317
  service:
    pipelines:
      traces:
        receivers: [zipkin]
        exporters: [otlp]
      metrics:
        receivers: [prometheus]
        exporters: [otlp]