新增 5 篇深度文档: - Lua 模块深度指南:OpenResty、ngx_lua、cosocket - 安全深度指南:WAF、DDoS 防护、OWASP Top 10 - API 网关配置:路由设计、JWT 验证、限流配额 - 动态配置与服务发现:etcd/Consul、nginx-unit Co-Authored-By: Claude <noreply@anthropic.com>
1528 lines
37 KiB
Markdown
1528 lines
37 KiB
Markdown
# NGINX API 网关配置指南
|
||
|
||
## 1. API 网关概述
|
||
|
||
### 什么是 API 网关
|
||
|
||
API 网关是微服务架构中的关键组件,作为单一入口点统一管理和暴露后端服务。NGINX 作为高性能反向代理,非常适合构建功能完善的 API 网关。
|
||
|
||
### API 网关核心功能
|
||
|
||
| 功能 | 说明 |
|
||
|------|------|
|
||
| **请求路由** | 根据路径、方法、Header 路由到不同后端服务 |
|
||
| **负载均衡** | 在多个服务实例间分发请求 |
|
||
| **认证授权** | JWT、OAuth2、API Key 验证 |
|
||
| **限流熔断** | 防止过载和服务雪崩 |
|
||
| **协议转换** | HTTP/HTTPS、WebSocket、gRPC 转换 |
|
||
| **请求/响应转换** | 修改请求头、响应体、路径重写 |
|
||
| **缓存加速** | 缓存常用 API 响应 |
|
||
| **日志监控** | 统一收集 API 调用日志和指标 |
|
||
|
||
### API 网关架构图
|
||
|
||
```
|
||
┌─────────────────┐
|
||
│ API Gateway │
|
||
│ (NGINX) │
|
||
└────────┬────────┘
|
||
│
|
||
┌─────────┬───────┴───────┬─────────┐
|
||
│ │ │ │
|
||
▼ ▼ ▼ ▼
|
||
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
|
||
│ User Svc │ │ Order Svc│ │PaymentSvc│ │ SearchSvc│
|
||
└──────────┘ └──────────┘ └──────────┘ └──────────┘
|
||
```
|
||
|
||
---
|
||
|
||
## 2. API 路由设计模式
|
||
|
||
### 2.1 基于路径的路由
|
||
|
||
最常见的路由方式,根据 URL 路径前缀路由到不同服务。
|
||
|
||
```nginx
|
||
http {
|
||
upstream user_service {
|
||
server user-svc:8080;
|
||
}
|
||
|
||
upstream order_service {
|
||
server order-svc:8081;
|
||
}
|
||
|
||
upstream payment_service {
|
||
server payment-svc:8082;
|
||
}
|
||
|
||
server {
|
||
listen 80;
|
||
server_name api.example.com;
|
||
|
||
# 用户服务路由
|
||
location /api/v1/users/ {
|
||
proxy_pass http://user_service/;
|
||
proxy_set_header Host $host;
|
||
proxy_set_header X-Real-IP $remote_addr;
|
||
}
|
||
|
||
# 订单服务路由
|
||
location /api/v1/orders/ {
|
||
proxy_pass http://order_service/;
|
||
proxy_set_header Host $host;
|
||
}
|
||
|
||
# 支付服务路由
|
||
location /api/v1/payments/ {
|
||
proxy_pass http://payment_service/;
|
||
proxy_set_header Host $host;
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### 2.2 基于请求方法的路由
|
||
|
||
同一路径根据 HTTP 方法路由到不同服务。
|
||
|
||
```nginx
|
||
server {
|
||
listen 80;
|
||
server_name api.example.com;
|
||
|
||
# 查询操作路由到只读服务
|
||
location /api/v1/data/ {
|
||
if ($request_method ~ ^(GET|HEAD)$) {
|
||
proxy_pass http://readonly_backend;
|
||
break;
|
||
}
|
||
|
||
# 写入操作路由到主服务
|
||
if ($request_method ~ ^(POST|PUT|PATCH|DELETE)$) {
|
||
proxy_pass http://write_backend;
|
||
break;
|
||
}
|
||
|
||
return 405; # Method Not Allowed
|
||
}
|
||
}
|
||
```
|
||
|
||
### 2.3 基于 Header 的路由
|
||
|
||
根据请求头内容路由,常用于 A/B 测试、金丝雀发布。
|
||
|
||
```nginx
|
||
http {
|
||
map $http_x_api_version $backend_pool {
|
||
default "stable_backend";
|
||
"v2" "beta_backend";
|
||
"v2-beta" "beta_backend";
|
||
}
|
||
|
||
upstream stable_backend {
|
||
server api-v1:8080;
|
||
}
|
||
|
||
upstream beta_backend {
|
||
server api-v2:8080;
|
||
}
|
||
|
||
server {
|
||
listen 80;
|
||
server_name api.example.com;
|
||
|
||
location /api/ {
|
||
proxy_pass http://$backend_pool;
|
||
proxy_set_header Host $host;
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### 2.4 基于 Cookie 的路由
|
||
|
||
```nginx
|
||
http {
|
||
map $cookie_app_version $backend_node {
|
||
default "stable";
|
||
"beta" "canary";
|
||
}
|
||
|
||
upstream stable {
|
||
server api-stable:8080;
|
||
}
|
||
|
||
upstream canary {
|
||
server api-canary:8080;
|
||
}
|
||
|
||
server {
|
||
listen 80;
|
||
server_name api.example.com;
|
||
|
||
location /api/ {
|
||
proxy_pass http://$backend_node;
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### 2.5 流量分流路由(百分比)
|
||
|
||
```nginx
|
||
http {
|
||
# 使用 split_clients 进行百分比分流
|
||
split_clients "${remote_addr}${http_user_agent}" $variant {
|
||
10% canary; # 10% 流量到新版本
|
||
* stable; # 90% 流量到稳定版
|
||
}
|
||
|
||
upstream stable {
|
||
server api-v1:8080;
|
||
}
|
||
|
||
upstream canary {
|
||
server api-v2:8080;
|
||
}
|
||
|
||
server {
|
||
listen 80;
|
||
server_name api.example.com;
|
||
|
||
location /api/ {
|
||
proxy_pass http://$variant;
|
||
proxy_set_header X-Variant $variant;
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### 2.6 组合路由策略
|
||
|
||
```nginx
|
||
http {
|
||
# 定义映射变量
|
||
map $http_x_env $target_backend {
|
||
default prod;
|
||
"staging" staging;
|
||
"dev" dev;
|
||
}
|
||
|
||
map $http_x_tenant_id $tenant_shard {
|
||
default shard1;
|
||
~^1[0-5] shard1; # 租户 10-15
|
||
~^1[6-9]|^2[0] shard2; # 租户 16-20
|
||
}
|
||
|
||
upstream prod {
|
||
server api-prod-1:8080;
|
||
server api-prod-2:8080;
|
||
}
|
||
|
||
upstream staging {
|
||
server api-staging:8080;
|
||
}
|
||
|
||
upstream dev {
|
||
server api-dev:8080;
|
||
}
|
||
|
||
upstream shard1 {
|
||
server db-shard1:8080;
|
||
}
|
||
|
||
upstream shard2 {
|
||
server db-shard2:8080;
|
||
}
|
||
|
||
server {
|
||
listen 80;
|
||
server_name api.example.com;
|
||
|
||
# 环境路由
|
||
location /api/ {
|
||
proxy_pass http://$target_backend;
|
||
proxy_set_header X-Tenant-Shard $tenant_shard;
|
||
|
||
# 记录路由决策
|
||
access_log /var/log/nginx/api-access.log detailed;
|
||
}
|
||
|
||
# 租户数据路由
|
||
location /api/tenant-data/ {
|
||
proxy_pass http://$tenant_shard;
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 3. 请求/响应转换
|
||
|
||
### 3.1 请求头转换
|
||
|
||
#### 添加请求头
|
||
|
||
```nginx
|
||
server {
|
||
listen 80;
|
||
server_name api.example.com;
|
||
|
||
location /api/ {
|
||
# 传递客户端信息
|
||
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;
|
||
proxy_set_header X-Forwarded-Host $host;
|
||
proxy_set_header X-Forwarded-Port $server_port;
|
||
|
||
# 添加网关标识
|
||
proxy_set_header X-API-Gateway "nginx";
|
||
proxy_set_header X-Request-ID $request_id;
|
||
|
||
# 添加时间戳
|
||
proxy_set_header X-Request-Time $msec;
|
||
|
||
proxy_pass http://backend;
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 删除请求头
|
||
|
||
```nginx
|
||
server {
|
||
location /api/ {
|
||
# 删除敏感请求头,防止信息泄露
|
||
proxy_set_header Authorization "";
|
||
proxy_set_header Cookie "";
|
||
proxy_set_header X-Internal-Token "";
|
||
|
||
proxy_pass http://backend;
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 修改请求头
|
||
|
||
```nginx
|
||
server {
|
||
location /api/ {
|
||
# 重写 Host 头
|
||
proxy_set_header Host backend.internal.com;
|
||
|
||
# 基于条件设置
|
||
if ($http_x_client_type = "mobile") {
|
||
proxy_set_header X-Device-Type "mobile";
|
||
}
|
||
|
||
proxy_pass http://backend;
|
||
}
|
||
}
|
||
```
|
||
|
||
### 3.2 响应头转换
|
||
|
||
#### 添加安全响应头
|
||
|
||
```nginx
|
||
server {
|
||
listen 80;
|
||
server_name api.example.com;
|
||
|
||
# 添加 API 响应头
|
||
add_header X-API-Version "v1" always;
|
||
add_header X-RateLimit-Limit "1000" always;
|
||
|
||
# 安全头部
|
||
add_header X-Content-Type-Options "nosniff" always;
|
||
add_header X-Frame-Options "DENY" always;
|
||
add_header X-XSS-Protection "1; mode=block" always;
|
||
|
||
# CORS 头
|
||
add_header Access-Control-Allow-Origin "*" always;
|
||
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" always;
|
||
add_header Access-Control-Allow-Headers "Authorization, Content-Type, X-Request-ID" always;
|
||
|
||
location /api/ {
|
||
proxy_pass http://backend;
|
||
|
||
# 暴露额外响应头给前端
|
||
expose_headers X-Request-ID X-RateLimit-Remaining;
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 隐藏响应头
|
||
|
||
```nginx
|
||
server {
|
||
location /api/ {
|
||
proxy_pass http://backend;
|
||
|
||
# 隐藏后端服务器信息
|
||
proxy_hide_header X-Powered-By;
|
||
proxy_hide_header X-Runtime;
|
||
proxy_hide_header X-Version;
|
||
proxy_hide_header Server;
|
||
}
|
||
}
|
||
```
|
||
|
||
### 3.3 请求体转换
|
||
|
||
#### 请求体大小限制
|
||
|
||
```nginx
|
||
http {
|
||
# 全局请求体限制
|
||
client_max_body_size 10m;
|
||
client_body_buffer_size 128k;
|
||
|
||
server {
|
||
listen 80;
|
||
server_name api.example.com;
|
||
|
||
# 上传接口允许更大请求体
|
||
location /api/v1/upload/ {
|
||
client_max_body_size 100m;
|
||
proxy_pass http://upload_backend;
|
||
}
|
||
|
||
# Webhook 接口限制较小
|
||
location /api/v1/webhooks/ {
|
||
client_max_body_size 1m;
|
||
proxy_pass http://webhook_backend;
|
||
}
|
||
|
||
# 普通 API
|
||
location /api/ {
|
||
client_max_body_size 5m;
|
||
proxy_pass http://api_backend;
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### 3.4 响应体转换(sub_filter)
|
||
|
||
#### 修改响应内容
|
||
|
||
```nginx
|
||
server {
|
||
listen 80;
|
||
server_name api.example.com;
|
||
|
||
location /api/ {
|
||
proxy_pass http://backend;
|
||
|
||
# 替换响应中的内部 URL 为外部 URL
|
||
sub_filter_once off;
|
||
sub_filter_types application/json;
|
||
|
||
# 替换后端地址为网关地址
|
||
sub_filter 'http://backend-internal:8080' 'https://api.example.com';
|
||
|
||
# 替换版本标识
|
||
sub_filter '"version": "internal"' '"version": "public"';
|
||
}
|
||
}
|
||
```
|
||
|
||
#### JSON 字段脱敏
|
||
|
||
```nginx
|
||
server {
|
||
location /api/users/ {
|
||
proxy_pass http://user_backend;
|
||
|
||
# 脱敏手机号(示例:隐藏中间4位)
|
||
sub_filter_once off;
|
||
sub_filter_types application/json;
|
||
sub_filter '([0-9]{3})[0-9]{4}([0-9]{4})' '$1****$2';
|
||
}
|
||
}
|
||
```
|
||
|
||
### 3.5 URL 重写与转换
|
||
|
||
#### 路径重写
|
||
|
||
```nginx
|
||
server {
|
||
listen 80;
|
||
server_name api.example.com;
|
||
|
||
# 旧版本路径兼容
|
||
location /api/v0/ {
|
||
rewrite ^/api/v0/(.*)$ /api/v1/$1 permanent;
|
||
}
|
||
|
||
# 内部路径映射
|
||
location /api/public/ {
|
||
rewrite ^/api/public/(.*)$ /internal/api/$1 break;
|
||
proxy_pass http://backend;
|
||
}
|
||
|
||
# 带参数重写
|
||
location /api/search/ {
|
||
rewrite ^/api/search/(.*)$ /search?q=$1 break;
|
||
proxy_pass http://search_backend;
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 查询参数处理
|
||
|
||
```nginx
|
||
server {
|
||
location /api/ {
|
||
# 添加默认参数
|
||
if ($arg_version = "") {
|
||
set $args $args&version=v1;
|
||
}
|
||
|
||
# 移除敏感参数
|
||
if ($arg_api_key) {
|
||
set $args '';
|
||
# 或者保留其他参数
|
||
set $args $arg_foo=$arg_foo;
|
||
}
|
||
|
||
proxy_pass http://backend;
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 4. JWT 验证
|
||
|
||
### 4.1 通过 auth_request 进行 JWT 验证
|
||
|
||
使用外部认证服务验证 JWT Token。
|
||
|
||
```nginx
|
||
http {
|
||
upstream api_backend {
|
||
server api:8080;
|
||
}
|
||
|
||
upstream auth_service {
|
||
server auth:8080;
|
||
}
|
||
|
||
server {
|
||
listen 80;
|
||
server_name api.example.com;
|
||
|
||
location /api/ {
|
||
# JWT 验证
|
||
auth_request /auth_verify;
|
||
|
||
# 从认证响应中提取用户信息
|
||
auth_request_set $auth_user $upstream_http_x_user_id;
|
||
auth_request_set $auth_role $upstream_http_x_user_role;
|
||
auth_request_set $auth_tenant $upstream_http_x_tenant_id;
|
||
|
||
# 传递给后端
|
||
proxy_set_header X-User-ID $auth_user;
|
||
proxy_set_header X-User-Role $auth_role;
|
||
proxy_set_header X-Tenant-ID $auth_tenant;
|
||
|
||
proxy_pass http://api_backend;
|
||
}
|
||
|
||
# JWT 验证子请求
|
||
location = /auth_verify {
|
||
internal;
|
||
proxy_pass http://auth_service/verify;
|
||
proxy_pass_request_body off;
|
||
proxy_set_header Content-Length "";
|
||
|
||
# 传递 Authorization 头
|
||
proxy_set_header Authorization $http_authorization;
|
||
proxy_set_header X-Original-URI $request_uri;
|
||
proxy_set_header X-Client-IP $remote_addr;
|
||
|
||
proxy_connect_timeout 3s;
|
||
proxy_read_timeout 3s;
|
||
}
|
||
|
||
# 认证失败处理
|
||
error_page 401 = @unauthorized;
|
||
location @unauthorized {
|
||
default_type application/json;
|
||
return 401 '{"error":"Unauthorized","code":"INVALID_TOKEN"}';
|
||
}
|
||
|
||
error_page 403 = @forbidden;
|
||
location @forbidden {
|
||
default_type application/json;
|
||
return 403 '{"error":"Forbidden","code":"INSUFFICIENT_PERMISSIONS"}';
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### 4.2 使用 Lua 进行 JWT 验证(OpenResty)
|
||
|
||
```nginx
|
||
http {
|
||
lua_package_path "/usr/local/lib/lua/?.lua;;";
|
||
|
||
server {
|
||
listen 80;
|
||
server_name api.example.com;
|
||
|
||
location /api/ {
|
||
access_by_lua_block {
|
||
local jwt = require "resty.jwt"
|
||
local validators = require "resty.jwt-validators"
|
||
|
||
-- 获取 token
|
||
local auth_header = ngx.var.http_authorization
|
||
if not auth_header then
|
||
ngx.status = 401
|
||
ngx.say('{"error":"Missing Authorization header"}')
|
||
ngx.exit(ngx.HTTP_UNAUTHORIZED)
|
||
end
|
||
|
||
local token = string.gsub(auth_header, "Bearer ", "")
|
||
|
||
-- 验证 JWT
|
||
local jwt_obj = jwt:verify(
|
||
ngx.var.jwt_secret, -- JWT 密钥
|
||
token,
|
||
{
|
||
iss = "https://auth.example.com",
|
||
validators = {
|
||
exp = validators.opt_is_not_expired(),
|
||
iat = validators.opt_is_not_before_now(),
|
||
}
|
||
}
|
||
)
|
||
|
||
if not jwt_obj.verified then
|
||
ngx.status = 401
|
||
ngx.say('{"error":"Invalid token","details":"' .. jwt_obj.reason .. '"}')
|
||
ngx.exit(ngx.HTTP_UNAUTHORIZED)
|
||
end
|
||
|
||
-- 设置变量供后续使用
|
||
ngx.var.jwt_sub = jwt_obj.payload.sub
|
||
ngx.var.jwt_role = jwt_obj.payload.role or "user"
|
||
}
|
||
|
||
proxy_set_header X-User-ID $jwt_sub;
|
||
proxy_set_header X-User-Role $jwt_role;
|
||
proxy_pass http://api_backend;
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### 4.3 使用 NJS(NGINX JavaScript)进行 JWT 验证
|
||
|
||
```nginx
|
||
load_module modules/ngx_http_js_module.so;
|
||
|
||
http {
|
||
js_import /etc/nginx/jwt_verify.js;
|
||
|
||
server {
|
||
listen 80;
|
||
server_name api.example.com;
|
||
|
||
location /api/ {
|
||
# 调用 NJS 验证
|
||
js_set $jwt_payload jwt_verify.verify;
|
||
|
||
# 验证失败时拒绝
|
||
if ($jwt_payload = "") {
|
||
return 401 '{"error":"Unauthorized"}';
|
||
}
|
||
|
||
proxy_set_header X-JWT-Payload $jwt_payload;
|
||
proxy_pass http://api_backend;
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
**jwt_verify.js**:
|
||
|
||
```javascript
|
||
function verify(r) {
|
||
var auth = r.headersIn['Authorization'];
|
||
if (!auth || !auth.startsWith('Bearer ')) {
|
||
return '';
|
||
}
|
||
|
||
var token = auth.substring(7);
|
||
|
||
try {
|
||
// 简单 JWT 解析(base64 解码 payload)
|
||
var parts = token.split('.');
|
||
if (parts.length !== 3) {
|
||
return '';
|
||
}
|
||
|
||
// 解码 payload
|
||
var payload = parts[1];
|
||
// base64url 解码
|
||
var decoded = Buffer.from(payload, 'base64url').toString();
|
||
var claims = JSON.parse(decoded);
|
||
|
||
// 验证过期时间
|
||
if (claims.exp && claims.exp < Math.floor(Date.now() / 1000)) {
|
||
return '';
|
||
}
|
||
|
||
return JSON.stringify(claims);
|
||
} catch (e) {
|
||
return '';
|
||
}
|
||
}
|
||
|
||
export default {verify};
|
||
```
|
||
|
||
### 4.4 JWT 验证配置参考表
|
||
|
||
| 验证方式 | 优点 | 缺点 | 适用场景 |
|
||
|----------|------|------|----------|
|
||
| **auth_request** | 灵活、业务逻辑外置、支持复杂验证 | 额外网络请求、延迟增加 | 需要与认证中心实时交互 |
|
||
| **Lua (OpenResty)** | 高性能、功能丰富、社区成熟 | 需要 OpenResty 或 lua-nginx-module | 复杂验证逻辑、本地处理 |
|
||
| **NJS** | NGINX 官方支持、无需额外模块 | 功能相对简单、性能略低 | 简单验证、官方生态优先 |
|
||
|
||
---
|
||
|
||
## 5. 限流与配额管理
|
||
|
||
### 5.1 基础限流配置
|
||
|
||
```nginx
|
||
http {
|
||
# 按 IP 限流区域
|
||
limit_req_zone $binary_remote_addr zone=ip_limit:10m rate=10r/s;
|
||
|
||
# 按用户限流区域
|
||
limit_req_zone $http_x_user_id zone=user_limit:10m rate=100r/m;
|
||
|
||
# 全局 API 限流
|
||
limit_req_zone $server_name zone=api_global:10m rate=1000r/s;
|
||
|
||
server {
|
||
listen 80;
|
||
server_name api.example.com;
|
||
|
||
# 全局限流
|
||
limit_req zone=api_global burst=200 nodelay;
|
||
|
||
location /api/ {
|
||
# IP 级限流
|
||
limit_req zone=ip_limit burst=20 nodelay;
|
||
|
||
proxy_pass http://api_backend;
|
||
}
|
||
|
||
location /api/v1/users/ {
|
||
# 用户级限流
|
||
limit_req zone=user_limit burst=10 nodelay;
|
||
|
||
proxy_pass http://user_backend;
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### 5.2 分层限流策略
|
||
|
||
```nginx
|
||
http {
|
||
# 不同层级的限流区域
|
||
limit_req_zone $binary_remote_addr zone=ip:10m rate=30r/s;
|
||
limit_req_zone $http_x_api_key zone=api_key:10m rate=100r/s;
|
||
limit_req_zone $http_x_user_id zone=user:10m rate=60r/m;
|
||
limit_req_zone $server_name zone=global:10m rate=5000r/s;
|
||
|
||
server {
|
||
listen 80;
|
||
server_name api.example.com;
|
||
|
||
# 全局保护
|
||
limit_req zone=global burst=1000 nodelay;
|
||
|
||
# 健康检查端点不限流
|
||
location /health {
|
||
limit_req off;
|
||
access_log off;
|
||
return 200 '{"status":"ok"}';
|
||
}
|
||
|
||
# 公开 API - 仅 IP 限流
|
||
location /api/public/ {
|
||
limit_req zone=ip burst=50 nodelay;
|
||
proxy_pass http://public_backend;
|
||
}
|
||
|
||
# 认证 API - IP + API Key 双重限流
|
||
location /api/v1/ {
|
||
limit_req zone=ip burst=30 nodelay;
|
||
limit_req zone=api_key burst=100 nodelay;
|
||
proxy_pass http://api_backend;
|
||
}
|
||
|
||
# 用户级 API - 三层限流
|
||
location /api/v1/user/ {
|
||
limit_req zone=ip burst=20 nodelay;
|
||
limit_req zone=api_key burst=50 nodelay;
|
||
limit_req zone=user burst=10 nodelay;
|
||
proxy_pass http://user_backend;
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### 5.3 配额管理(基于连接数)
|
||
|
||
```nginx
|
||
http {
|
||
# 按 API Key 限制并发连接
|
||
limit_conn_zone $http_x_api_key zone=conn_by_key:10m;
|
||
|
||
# 按用户限制并发连接
|
||
limit_conn_zone $http_x_user_id zone=conn_by_user:10m;
|
||
|
||
server {
|
||
listen 80;
|
||
server_name api.example.com;
|
||
|
||
location /api/v1/stream/ {
|
||
# 每个 API Key 最多 10 个并发连接
|
||
limit_conn conn_by_key 10;
|
||
|
||
# 每个用户最多 5 个并发连接
|
||
limit_conn conn_by_user 5;
|
||
|
||
proxy_pass http://streaming_backend;
|
||
proxy_buffering off;
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### 5.4 限流响应自定义
|
||
|
||
```nginx
|
||
http {
|
||
limit_req_zone $binary_remote_addr zone=limit:10m rate=10r/s;
|
||
|
||
server {
|
||
listen 80;
|
||
server_name api.example.com;
|
||
|
||
location /api/ {
|
||
limit_req zone=limit burst=20 nodelay;
|
||
|
||
# 自定义限流响应
|
||
limit_req_status 429; # Too Many Requests
|
||
|
||
proxy_pass http://api_backend;
|
||
}
|
||
|
||
# 自定义 429 响应
|
||
error_page 429 @rate_limited;
|
||
location @rate_limited {
|
||
default_type application/json;
|
||
add_header Retry-After 60 always;
|
||
return 429 '{"error":"Rate limit exceeded","retry_after":60,"limit":10}';
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### 5.5 基于路径的差异化限流
|
||
|
||
```nginx
|
||
http {
|
||
# 不同限流区域
|
||
limit_req_zone $binary_remote_addr zone=light:10m rate=100r/m;
|
||
limit_req_zone $binary_remote_addr zone=medium:10m rate=10r/s;
|
||
limit_req_zone $binary_remote_addr zone=heavy:10m rate=1r/s;
|
||
|
||
map $uri $rate_limit_zone {
|
||
default "";
|
||
~*^/api/health "none";
|
||
~*^/api/search "heavy";
|
||
~*^/api/reports "heavy";
|
||
~*^/api/export "heavy";
|
||
~*^/api/webhooks "light";
|
||
}
|
||
|
||
server {
|
||
listen 80;
|
||
server_name api.example.com;
|
||
|
||
location /api/ {
|
||
# 根据路径应用不同限流
|
||
if ($rate_limit_zone = "none") {
|
||
limit_req off;
|
||
}
|
||
|
||
if ($rate_limit_zone = "light") {
|
||
limit_req zone=light burst=5 nodelay;
|
||
}
|
||
|
||
if ($rate_limit_zone = "heavy") {
|
||
limit_req zone=heavy burst=3 nodelay;
|
||
}
|
||
|
||
proxy_pass http://api_backend;
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### 5.6 限流指标暴露
|
||
|
||
```nginx
|
||
http {
|
||
limit_req_zone $binary_remote_addr zone=api:10m rate=100r/s;
|
||
|
||
server {
|
||
listen 80;
|
||
server_name api.example.com;
|
||
|
||
location /api/ {
|
||
limit_req zone=api burst=200 nodelay;
|
||
|
||
# 添加限流相关响应头
|
||
add_header X-RateLimit-Limit 100 always;
|
||
add_header X-RateLimit-Window 1s always;
|
||
|
||
proxy_pass http://api_backend;
|
||
}
|
||
|
||
# 限流状态端点
|
||
location /api/status/ratelimit {
|
||
limit_req off;
|
||
stub_status on;
|
||
access_log off;
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 6. API 版本控制策略
|
||
|
||
### 6.1 URL 路径版本控制
|
||
|
||
```nginx
|
||
http {
|
||
upstream api_v1 {
|
||
server api-v1:8080;
|
||
}
|
||
|
||
upstream api_v2 {
|
||
server api-v2:8080;
|
||
}
|
||
|
||
server {
|
||
listen 80;
|
||
server_name api.example.com;
|
||
|
||
# v1 路由
|
||
location /api/v1/ {
|
||
proxy_pass http://api_v1/;
|
||
proxy_set_header X-API-Version "v1";
|
||
}
|
||
|
||
# v2 路由
|
||
location /api/v2/ {
|
||
proxy_pass http://api_v2/;
|
||
proxy_set_header X-API-Version "v2";
|
||
}
|
||
|
||
# 默认版本(向后兼容)
|
||
location /api/ {
|
||
rewrite ^/api/(.*)$ /api/v2/$1 break;
|
||
proxy_pass http://api_v2;
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### 6.2 Header 版本控制
|
||
|
||
```nginx
|
||
http {
|
||
upstream api_v1 {
|
||
server api-v1:8080;
|
||
}
|
||
|
||
upstream api_v2 {
|
||
server api-v2:8080;
|
||
}
|
||
|
||
# 根据 Accept-Version 头路由
|
||
map $http_accept_version $api_version {
|
||
default "v2";
|
||
"1" "v1";
|
||
"2" "v2";
|
||
"v1" "v1";
|
||
"v2" "v2";
|
||
}
|
||
|
||
server {
|
||
listen 80;
|
||
server_name api.example.com;
|
||
|
||
location /api/ {
|
||
# 版本不存在时返回 400
|
||
if ($api_version = "") {
|
||
return 400 '{"error":"Unsupported API version"}';
|
||
}
|
||
|
||
proxy_pass http://api_$api_version;
|
||
proxy_set_header X-API-Version $api_version;
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### 6.3 内容协商版本控制
|
||
|
||
```nginx
|
||
http {
|
||
upstream api_v1 {
|
||
server api-v1:8080;
|
||
}
|
||
|
||
upstream api_v2 {
|
||
server api-v2:8080;
|
||
}
|
||
|
||
server {
|
||
listen 80;
|
||
server_name api.example.com;
|
||
|
||
location /api/users {
|
||
# 检查 Accept 头中的版本媒体类型
|
||
if ($http_accept ~ "application/vnd\.api\.v2\+json") {
|
||
proxy_pass http://api_v2;
|
||
break;
|
||
}
|
||
|
||
if ($http_accept ~ "application/vnd\.api\.v1\+json") {
|
||
proxy_pass http://api_v1;
|
||
break;
|
||
}
|
||
|
||
# 默认版本
|
||
proxy_pass http://api_v2;
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### 6.4 版本弃用与 Sunset
|
||
|
||
```nginx
|
||
http {
|
||
server {
|
||
listen 80;
|
||
server_name api.example.com;
|
||
|
||
location /api/v1/ {
|
||
# 添加弃用警告头
|
||
add_header Deprecation "true" always;
|
||
add_header Sunset "Sun, 31 Dec 2024 23:59:59 GMT" always;
|
||
add_header Link '</api/v2/>; rel="successor-version"' always;
|
||
|
||
proxy_pass http://api_v1;
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 7. OpenAPI/Swagger 集成
|
||
|
||
### 7.1 Swagger UI 托管
|
||
|
||
```nginx
|
||
http {
|
||
server {
|
||
listen 80;
|
||
server_name api.example.com;
|
||
|
||
# Swagger UI 静态文件
|
||
location /docs/ {
|
||
alias /var/www/swagger-ui/;
|
||
try_files $uri $uri/ /docs/index.html;
|
||
}
|
||
|
||
# OpenAPI 规范文件
|
||
location /api-docs/ {
|
||
alias /etc/nginx/api-docs/;
|
||
default_type application/json;
|
||
|
||
# CORS
|
||
add_header Access-Control-Allow-Origin "*" always;
|
||
add_header Access-Control-Allow-Methods "GET, OPTIONS" always;
|
||
}
|
||
|
||
# 特定服务文档
|
||
location /api-docs/users.yaml {
|
||
alias /etc/nginx/api-docs/users.yaml;
|
||
default_type text/yaml;
|
||
}
|
||
|
||
location /api-docs/orders.yaml {
|
||
alias /etc/nginx/api-docs/orders.yaml;
|
||
default_type text/yaml;
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### 7.2 多服务 API 文档聚合
|
||
|
||
```nginx
|
||
http {
|
||
server {
|
||
listen 80;
|
||
server_name api.example.com;
|
||
|
||
# 聚合文档入口
|
||
location /api-docs {
|
||
default_type application/json;
|
||
return 200 '{
|
||
"openapi": "3.0.0",
|
||
"info": {
|
||
"title": "API Gateway",
|
||
"version": "1.0.0"
|
||
},
|
||
"servers": [
|
||
{"url": "https://api.example.com"}
|
||
],
|
||
"paths": {
|
||
"/api/v1/users": {"$ref": "/api-docs/users.yaml#/paths/~1users"},
|
||
"/api/v1/orders": {"$ref": "/api-docs/orders.yaml#/paths/~1orders"}
|
||
}
|
||
}';
|
||
}
|
||
|
||
# 代理到各服务文档
|
||
location /api-docs/users/ {
|
||
proxy_pass http://user_service/docs/;
|
||
}
|
||
|
||
location /api-docs/orders/ {
|
||
proxy_pass http://order_service/docs/;
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### 7.3 API 文档访问控制
|
||
|
||
```nginx
|
||
http {
|
||
server {
|
||
listen 80;
|
||
server_name api.example.com;
|
||
|
||
# 公开文档
|
||
location /docs/public/ {
|
||
alias /var/www/docs/public/;
|
||
}
|
||
|
||
# 内部文档(需认证)
|
||
location /docs/internal/ {
|
||
auth_basic "Internal API Docs";
|
||
auth_basic_user_file /etc/nginx/.htpasswd-docs;
|
||
|
||
alias /var/www/docs/internal/;
|
||
}
|
||
|
||
# API 定义文件保护
|
||
location ~ ^/api-docs/.*\.(yaml|json)$ {
|
||
# 只允许特定 referer
|
||
valid_referers server_names *.example.com;
|
||
|
||
if ($invalid_referer) {
|
||
return 403;
|
||
}
|
||
|
||
alias /etc/nginx/api-docs/;
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 8. 完整 API 网关配置示例
|
||
|
||
### 8.1 生产级 API 网关配置
|
||
|
||
```nginx
|
||
# /etc/nginx/nginx.conf
|
||
|
||
user nginx;
|
||
worker_processes auto;
|
||
worker_rlimit_nofile 65535;
|
||
|
||
error_log /var/log/nginx/error.log warn;
|
||
pid /var/run/nginx.pid;
|
||
|
||
events {
|
||
worker_connections 4096;
|
||
use epoll;
|
||
multi_accept on;
|
||
}
|
||
|
||
http {
|
||
include /etc/nginx/mime.types;
|
||
default_type application/json;
|
||
|
||
# 日志格式
|
||
log_format api_log '$remote_addr - $remote_user [$time_local] '
|
||
'"$request" $status $body_bytes_sent '
|
||
'"$http_referer" "$http_user_agent" '
|
||
'rt=$request_time uct="$upstream_connect_time" '
|
||
'uht="$upstream_header_time" urt="$upstream_response_time" '
|
||
'req_id="$request_id" api_key="$http_x_api_key" '
|
||
'user_id="$jwt_sub" tenant="$jwt_tenant"';
|
||
|
||
access_log /var/log/nginx/access.log api_log;
|
||
|
||
# 性能优化
|
||
sendfile on;
|
||
tcp_nopush on;
|
||
tcp_nodelay on;
|
||
keepalive_timeout 65;
|
||
types_hash_max_size 2048;
|
||
|
||
# 客户端限制
|
||
client_max_body_size 10m;
|
||
client_body_buffer_size 128k;
|
||
client_header_buffer_size 4k;
|
||
large_client_header_buffers 4 8k;
|
||
|
||
# 限流区域
|
||
limit_req_zone $binary_remote_addr zone=ip:10m rate=30r/s;
|
||
limit_req_zone $http_x_api_key zone=api_key:10m rate=100r/s;
|
||
limit_req_zone $server_name zone=global:10m rate=10000r/s;
|
||
|
||
# 连接限制
|
||
limit_conn_zone $binary_remote_addr zone=addr:10m;
|
||
limit_conn_zone $http_x_api_key zone=api_conn:10m;
|
||
|
||
# 上游服务定义
|
||
upstream user_service {
|
||
zone user_service 64k;
|
||
least_conn;
|
||
server user-svc-1:8080 weight=5;
|
||
server user-svc-2:8080 weight=5;
|
||
server user-svc-3:8080 backup;
|
||
keepalive 32;
|
||
}
|
||
|
||
upstream order_service {
|
||
zone order_service 64k;
|
||
least_conn;
|
||
server order-svc-1:8080;
|
||
server order-svc-2:8080;
|
||
keepalive 32;
|
||
}
|
||
|
||
upstream payment_service {
|
||
zone payment_service 64k;
|
||
server payment-svc-1:8080 max_fails=3 fail_timeout=30s;
|
||
server payment-svc-2:8080 max_fails=3 fail_timeout=30s;
|
||
keepalive 16;
|
||
}
|
||
|
||
upstream auth_service {
|
||
zone auth_service 64k;
|
||
server auth-svc:8080;
|
||
keepalive 16;
|
||
}
|
||
|
||
# 变量映射
|
||
map $http_x_api_version $api_version {
|
||
default "v1";
|
||
"v1" "v1";
|
||
"v2" "v2";
|
||
}
|
||
|
||
map $uri $rate_limit_tier {
|
||
default "medium";
|
||
~*^/api/health "none";
|
||
~*^/api/metrics "none";
|
||
~*^/api/export "strict";
|
||
~*^/api/search "strict";
|
||
~*^/api/webhooks "relaxed";
|
||
}
|
||
|
||
# API Gateway 服务器
|
||
server {
|
||
listen 80;
|
||
listen [::]:80;
|
||
server_name api.example.com;
|
||
|
||
# 返回 444 关闭非指定域名访问
|
||
location / {
|
||
return 444;
|
||
}
|
||
|
||
# 健康检查(无认证、无限流)
|
||
location = /health {
|
||
access_log off;
|
||
limit_req off;
|
||
return 200 '{"status":"healthy","gateway":"nginx"}';
|
||
}
|
||
|
||
# Prometheus 指标端点
|
||
location = /metrics {
|
||
access_log off;
|
||
limit_req off;
|
||
stub_status on;
|
||
}
|
||
|
||
# API 文档
|
||
location /docs/ {
|
||
alias /var/www/api-docs/;
|
||
try_files $uri $uri/ =404;
|
||
|
||
# 缓存文档
|
||
expires 1h;
|
||
add_header Cache-Control "public, immutable";
|
||
}
|
||
|
||
# 主 API 入口
|
||
location /api/ {
|
||
# 请求 ID 生成
|
||
add_header X-Request-ID $request_id always;
|
||
|
||
# 全局限流
|
||
limit_req zone=global burst=2000 nodelay;
|
||
|
||
# 分层限流
|
||
limit_req zone=ip burst=50 nodelay;
|
||
limit_req zone=api_key burst=100 nodelay;
|
||
|
||
# 连接限制
|
||
limit_conn addr 50;
|
||
limit_conn api_conn 100;
|
||
|
||
# JWT 验证(可选,根据路径)
|
||
location ~ ^/api/(v1|v2)/(users|orders|payments)/ {
|
||
auth_request /auth/verify;
|
||
auth_request_set $jwt_sub $upstream_http_x_user_id;
|
||
auth_request_set $jwt_role $upstream_http_x_user_role;
|
||
auth_request_set $jwt_tenant $upstream_http_x_tenant_id;
|
||
|
||
# 认证失败处理
|
||
error_page 401 = @auth_error;
|
||
error_page 403 = @forbidden_error;
|
||
|
||
# 子路径路由
|
||
location ~ ^/api/(v1|v2)/users/ {
|
||
rewrite ^/api/(v1|v2)/(.*)$ /$2 break;
|
||
proxy_pass http://user_service;
|
||
}
|
||
|
||
location ~ ^/api/(v1|v2)/orders/ {
|
||
rewrite ^/api/(v1|v2)/(.*)$ /$2 break;
|
||
proxy_pass http://order_service;
|
||
}
|
||
|
||
location ~ ^/api/(v1|v2)/payments/ {
|
||
rewrite ^/api/(v1|v2)/(.*)$ /$2 break;
|
||
proxy_pass http://payment_service;
|
||
}
|
||
}
|
||
|
||
# 公开 API(无需认证)
|
||
location ~ ^/api/(v1|v2)/public/ {
|
||
rewrite ^/api/(v1|v2)/(.*)$ /$2 break;
|
||
proxy_pass http://public_service;
|
||
}
|
||
}
|
||
|
||
# 认证子请求
|
||
location = /auth/verify {
|
||
internal;
|
||
proxy_pass http://auth_service/verify;
|
||
proxy_pass_request_body off;
|
||
proxy_set_header Content-Length "";
|
||
proxy_set_header Authorization $http_authorization;
|
||
proxy_set_header X-Original-URI $request_uri;
|
||
proxy_set_header X-Original-Method $request_method;
|
||
proxy_connect_timeout 3s;
|
||
proxy_read_timeout 3s;
|
||
}
|
||
|
||
# WebSocket 支持
|
||
location /ws/ {
|
||
proxy_pass http://websocket_backend;
|
||
proxy_http_version 1.1;
|
||
proxy_set_header Upgrade $http_upgrade;
|
||
proxy_set_header Connection "upgrade";
|
||
proxy_set_header Host $host;
|
||
proxy_set_header X-Real-IP $remote_addr;
|
||
proxy_read_timeout 86400s;
|
||
proxy_send_timeout 86400s;
|
||
}
|
||
|
||
# 错误处理
|
||
error_page 500 502 503 504 @api_error;
|
||
location @api_error {
|
||
internal;
|
||
default_type application/json;
|
||
add_header X-Error-Source "gateway" always;
|
||
return 500 '{"error":"Internal Server Error","code":"GATEWAY_ERROR"}';
|
||
}
|
||
|
||
location @auth_error {
|
||
internal;
|
||
default_type application/json;
|
||
return 401 '{"error":"Unauthorized","code":"AUTH_REQUIRED"}';
|
||
}
|
||
|
||
location @forbidden_error {
|
||
internal;
|
||
default_type application/json;
|
||
return 403 '{"error":"Forbidden","code":"ACCESS_DENIED"}';
|
||
}
|
||
|
||
location @rate_limited {
|
||
internal;
|
||
default_type application/json;
|
||
add_header Retry-After 60 always;
|
||
return 429 '{"error":"Rate limit exceeded","code":"RATE_LIMITED","retry_after":60}';
|
||
}
|
||
}
|
||
|
||
# HTTPS 服务器
|
||
server {
|
||
listen 443 ssl http2;
|
||
listen [::]:443 ssl http2;
|
||
server_name api.example.com;
|
||
|
||
# SSL 配置
|
||
ssl_certificate /etc/nginx/ssl/api.example.com.crt;
|
||
ssl_certificate_key /etc/nginx/ssl/api.example.com.key;
|
||
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;
|
||
ssl_prefer_server_ciphers off;
|
||
ssl_session_cache shared:SSL:10m;
|
||
ssl_session_timeout 1d;
|
||
ssl_session_tickets off;
|
||
|
||
# HSTS
|
||
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" 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;
|
||
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||
|
||
# 重用 HTTP 配置
|
||
include /etc/nginx/api-locations.conf;
|
||
}
|
||
}
|
||
```
|
||
|
||
### 8.2 配置结构建议
|
||
|
||
```
|
||
/etc/nginx/
|
||
├── nginx.conf # 主配置
|
||
├── conf.d/
|
||
│ ├── 00-upstreams.conf # 上游服务定义
|
||
│ ├── 10-limits.conf # 限流配置
|
||
│ ├── 20-maps.conf # 变量映射
|
||
│ └── 30-ssl.conf # SSL 通用配置
|
||
├── sites-enabled/
|
||
│ └── api-gateway.conf # API 网关服务器配置
|
||
├── api-docs/ # API 文档
|
||
│ ├── openapi.yaml
|
||
│ ├── users.yaml
|
||
│ └── orders.yaml
|
||
└── ssl/
|
||
├── api.example.com.crt
|
||
└── api.example.com.key
|
||
```
|
||
|
||
### 8.3 常用指令速查表
|
||
|
||
| 类别 | 指令 | 说明 |
|
||
|------|------|------|
|
||
| **路由** | `proxy_pass` | 代理到后端 |
|
||
| | `rewrite` | URL 重写 |
|
||
| | `map` | 变量映射 |
|
||
| **请求头** | `proxy_set_header` | 设置代理请求头 |
|
||
| | `proxy_hide_header` | 隐藏响应头 |
|
||
| | `add_header` | 添加响应头 |
|
||
| **认证** | `auth_request` | 外部认证 |
|
||
| | `auth_request_set` | 提取认证变量 |
|
||
| **限流** | `limit_req_zone` | 定义限流区域 |
|
||
| | `limit_req` | 应用限流 |
|
||
| | `limit_conn_zone` | 定义连接限制区域 |
|
||
| | `limit_conn` | 应用连接限制 |
|
||
| **转换** | `sub_filter` | 响应内容替换 |
|
||
| | `client_max_body_size` | 请求体大小限制 |
|
||
|
||
---
|
||
|
||
## 9. 最佳实践
|
||
|
||
### 9.1 安全最佳实践
|
||
|
||
1. **始终使用 HTTPS**:生产环境强制 TLSv1.2+
|
||
2. **隐藏后端信息**:移除 Server、X-Powered-By 等响应头
|
||
3. **启用 HSTS**:防止降级攻击
|
||
4. **实施认证**:敏感接口必须认证
|
||
5. **输入验证**:限制请求体大小和方法
|
||
|
||
### 9.2 性能最佳实践
|
||
|
||
1. **启用 keepalive**:减少连接建立开销
|
||
2. **合理配置缓冲区**:平衡内存使用和响应速度
|
||
3. **使用缓存**:对读多写少的 API 启用缓存
|
||
4. **连接池**:配置上游 keepalive 连接
|
||
5. **启用 gzip**:压缩 JSON 响应
|
||
|
||
### 9.3 可观测性最佳实践
|
||
|
||
1. **结构化日志**:包含请求 ID、用户 ID、耗时等
|
||
2. **健康检查端点**:便于负载均衡器探测
|
||
3. **指标暴露**:使用 stub_status 或 nginx-module-vts
|
||
4. **分布式追踪**:传递 Trace ID 到后端
|
||
5. **告警配置**:对错误率和延迟设置告警
|
||
|
||
### 9.4 部署检查清单
|
||
|
||
- [ ] 配置文件语法验证 (`nginx -t`)
|
||
- [ ] 限流阈值合理性验证
|
||
- [ ] SSL 证书有效性检查
|
||
- [ ] 后端服务连通性测试
|
||
- [ ] 认证流程端到端测试
|
||
- [ ] 错误响应格式验证
|
||
- [ ] 性能基准测试
|
||
- [ ] 安全扫描(端口、Header 等)
|