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: falseapisix-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 网关安全配置对比
| 功能 | Kong | APISIX | Nginx |
|---|---|---|---|
| 认证插件 | JWT, Key Auth, OAuth2, LDAP | JWT Auth, Key Auth, Authz | 需要 Lua 脚本 |
| 限流 | 内置插件 | 内置插件 | ngx_http_limit_req_module |
| 熔断 | Circuit Breaker 插件 | Circuit Breaker 插件 | 需要模块组合 |
| WAF | 需集成第三方 | 需集成 | ModSecurity |
| 配置方式 | Declarative YAML | Declarative YAML | NGINX conf |
| 扩展性 | Lua/Go 插件 | Lua/WASM 插件 | Lua 模块 |
| 性能 | 高 | 极高 | 极高 |
| 学习曲线 | 中等 | 中等 | 陡峭 |
#安全配置推荐
| 场景 | 推荐方案 | 理由 |
|---|---|---|
| 快速起步 | Kong | 丰富的插件生态,开箱即用 |
| 高性能要求 | APISIX | 基于 Apache APISIX,性能优异 |
| 简单场景 | Nginx | 配置简单,资源占用低 |
| 企业级 | Kong + WAF | Kong 插件丰富 + 专业 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 网关的认证和后端服务的认证应该如何分工?哪些应该在网关层做,哪些应该在后端做?
参考答案
认证分层原则:
网关层(必须做):
- TLS 终止:所有请求在网关层解密
- 基础 Token 验证:验证 Token 格式、签名、有效期
- IP 黑名单/白名单:网络层访问控制
- 限流:防止 DoS
- 日志记录:记录原始请求信息
网关层(可选做):
- 完整权限检查:如果权限逻辑简单且统一
- 敏感数据过滤:响应体过滤
后端服务(必须做):
- 业务权限检查:用户与资源的所有权关系
- 细粒度授权:字段级、参数级权限
- 审计日志:业务操作的详细日志
后端服务(推荐做):
- 参数验证:业务逻辑相关的参数校验
- 数据脱敏:根据用户角色返回不同字段
实际建议:
网关层:验证 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"何时优化:
- 网关延迟 > 后端延迟 20%
- 网关成为瓶颈(CPU 打满)
- 吞吐量不满足需求
问题 3:在多云或混合云环境下,如何统一管理多个 API 网关的安全配置?
参考答案
多云 API 网关管理挑战:
- 配置一致性:不同云平台的 API 网关配置语法不同
- 证书管理:多云环境证书分发复杂
- 密钥管理:跨云密钥同步困难
- 监控告警:需要统一视图
解决方案: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: 1002. 使用 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.username3. 统一密钥管理:
使用 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]