TURN凭据与防滥用
TURN 是 WebRTC 链路里最容易被白嫖的组件。它的本质是一个鉴权后的 UDP/TCP 中继代理,一旦凭据失守,攻击者可以用你的服务器中转任意流量:刷币、扫端口、撞库、做匿名出口节点。本文给出工程上经过生产验证的最小可用方案:短期凭据 + 配额 + peer-ip 黑白名单 + 监控告警。
适用读者:负责 WebRTC 信令、媒体后端、SRE、安全合规岗位。默认你已经理解 ICE/STUN/TURN 三件套以及 coturn 的基本部署。
1. 为什么 TURN 凭据必须短期
1.1 长期凭据的真实事故面
- 被爬虫白嫖:前端硬编码
username/credential等于把 TURN 公开。GitHub、Wayback Machine、CDN 缓存都会留痕,几小时内会被扫描器收录。 - 匿名出口代理:TURN 默认转发任意 UDP/TCP,攻击者可以把你的 TURN 当成 SOCKS5 跳板做扫描、撞库、攻击第三方。出口 IP 是你的,背锅的是你。
- 刷流量打爆账单:云上 TURN 走的是公网带宽,攻击者用 100 Mbps 持续中继 24 小时,单台机器一天就能烧掉数百到数千元。
- 无法吊销:长期凭据没有过期机制,一旦泄露,唯一止损方式是改 secret 然后让所有真实用户重连——这等于一次故障。
1.2 短期凭据解决了什么
- TTL 内自爆:5~10 分钟过期,泄露的凭据攻击窗口极短。
- 无需服务端存储:HMAC 派生,coturn 本地校验,签发服务无状态。
- 可绑定业务身份:
username里嵌入 userId/roomId,后期审计能定位到人。 - 吊销简单:直接换
static-auth-secret,所有未过期凭据立即失效。
1.3 什么时候不适用
- 完全私有的内网 TURN(无公网入口、IP 白名单严格):长期凭据风险可控,但仍建议短期,统一运维心智。
- 设备类场景(IoT 摄像头)凭据更新链路不稳:可以适当拉长 TTL 到 1~6 小时,但不要超过 24 小时,且必须配合
user-quota。
1.4 失败时的样子
- 客户端日志大量
401 Unauthorized或438 Stale Nonce:TTL 太短或时钟漂移。 - coturn 日志出现陌生的
peer ip段、目标端口非 UDP/TCP 标准媒体口:凭据已被滥用。 - 流量监控看到 TURN 入向带宽与房间内会话数严重不匹配:被当跳板了。
2. REST API 短期凭据机制(RFC 8489 附录 B)
2.0 端到端时序
2.1 协议定义
RFC 8489 附录 B 描述了一种"无需服务端会话状态"的短期凭据方案,coturn 通过 use-auth-secret 实现:
username = <expiry-unix-timestamp> ":" <identifier>
password = base64( HMAC-SHA1( shared-secret, username ) )
expiry-unix-timestamp:凭据失效的 Unix 时间戳(秒)。identifier:业务侧的用户/房间标识,仅用于审计和日志,coturn 不校验内容。shared-secret:业务后端与 coturn 共享的对称密钥,绝不下发到客户端。- HMAC 算法固定为 SHA-1(这里 SHA-1 仅做 MAC 用途,没有抗碰撞需求,不存在安全问题)。
校验时 coturn 自己用同一份 secret 重新计算 HMAC,比对 password;同时检查 expiry-unix-timestamp > now(),超时直接 401。
2.2 为什么是 HMAC-SHA1
- 无状态:服务端不需要维护"已签发凭据表",水平扩展简单。
- 签发开销极低:单核每秒可生成数十万条,签发服务可以做成单机、无依赖。
- 跨语言一致:HMAC-SHA1 在所有语言里行为完全一致,签发服务用 Node/Go/Python 都能和 C 实现的 coturn 对齐。
2.3 什么时候不适用
- 需要"一次一密"或"绑定到具体 ICE allocation"的强审计场景:标准 REST 方案给不了,需要走 OAuth 或自研账号体系。
- 需要按用户做带宽差异化(VIP 用户更高带宽):REST 方案里 quota 是 coturn 全局配置,做不到精细化,需要给不同用户挂不同 realm。
3. coturn 完整配置示例
3.1 turnserver.conf
# /etc/coturn/turnserver.conf
# coturn 4.6+,2026-06-22 核对
# ===== 监听 =====
listening-port=3478
tls-listening-port=5349
listening-ip=0.0.0.0
relay-ip=203.0.113.10 # 公网出向 IP,单网卡可省略
external-ip=203.0.113.10 # NAT 后必填,否则 srflx 候选错误
min-port=49152
max-port=65535
# ===== TLS(turns:)=====
cert=/etc/letsencrypt/live/turn.example.com/fullchain.pem
pkey=/etc/letsencrypt/live/turn.example.com/privkey.pem
cipher-list="ECDHE+AESGCM:ECDHE+CHACHA20:!aNULL:!MD5:!RC4"
no-tlsv1
no-tlsv1_1
# ===== 认证:REST API 短期凭据 =====
use-auth-secret
static-auth-secret=REPLACE_WITH_64_BYTES_RANDOM_HEX
realm=turn.example.com
# 关闭长期凭据,避免运维误开
no-auth-pings
# ===== 配额(防滥用核心)=====
# 单个 username 同时只能持有 12 个 allocation
user-quota=12
# 全服务最多 1200 个 allocation
total-quota=1200
# 全服务带宽上限,单位 kbps(这里 200 Mbps)
bps-capacity=200000
# 单个会话带宽上限,单位 bps(这里 2 Mbps,720p 视频足够)
max-bps=2000000
# ===== Peer IP 黑名单:防 SSRF =====
# 拒绝中继到任何内网 / 链路本地 / 回环 / 多播 / CGNAT
denied-peer-ip=0.0.0.0-0.255.255.255
denied-peer-ip=10.0.0.0-10.255.255.255
denied-peer-ip=100.64.0.0-100.127.255.255
denied-peer-ip=127.0.0.0-127.255.255.255
denied-peer-ip=169.254.0.0-169.254.255.255
denied-peer-ip=172.16.0.0-172.31.255.255
denied-peer-ip=192.0.0.0-192.0.0.255
denied-peer-ip=192.0.2.0-192.0.2.255
denied-peer-ip=192.168.0.0-192.168.255.255
denied-peer-ip=198.18.0.0-198.19.255.255
denied-peer-ip=198.51.100.0-198.51.100.255
denied-peer-ip=203.0.113.0-203.0.113.255
denied-peer-ip=224.0.0.0-239.255.255.255
denied-peer-ip=240.0.0.0-255.255.255.255
# IPv6 关键段
denied-peer-ip=::1
denied-peer-ip=fc00::-fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff
denied-peer-ip=fe80::-febf:ffff:ffff:ffff:ffff:ffff:ffff:ffff
# ===== 关闭无用功能(缩小攻击面)=====
no-cli # 关掉 telnet 控制台
no-multicast-peers # 拒绝多播 peer
no-loopback-peers # 拒绝 127.0.0.0/8 peer(与上面 denied-peer-ip 双保险)
no-rfc5780 # 关掉 NAT 行为发现,避免被探测
no-stun-backward-compatibility
secure-stun # STUN binding 也要鉴权
mobility # 允许 ICE mobility,移动端断网恢复
# ===== 日志 =====
log-file=/var/log/coturn/turnserver.log
syslog
verbose
# 生产建议关掉 verbose,改用 simple-log
# simple-log
# ===== 高可用 =====
fingerprint
lt-cred-mech # use-auth-secret 复用 lt 机制头部,必须开
no-stdout-log
pidfile=/var/run/turnserver.pid
proc-user=turnserver
proc-group=turnserver
关键点:
use-auth-secret与lt-cred-mech必须同时开启,coturn 把 REST 短期凭据当作长期凭据机制的一种特例处理。
3.2 配置项语义速查
| 配置 | 作用 | 失守后果 |
|---|---|---|
use-auth-secret | 启用 REST 短期凭据 | 不开就退化成长期凭据 |
static-auth-secret | HMAC 共享密钥 | 泄露=任意人签凭据 |
realm | 凭据所属域,多租户隔离 | 不设无法多租户复用集群 |
user-quota | 单 username 最大 allocation 数 | 不设单凭据可无限开会话 |
total-quota | 全服务最大 allocation | 不设容易 OOM |
bps-capacity | 全服务总带宽上限 (kbps) | 不设可被打爆账单 |
max-bps | 单会话带宽上限 (bps) | 不设单用户可吃满整机 |
denied-peer-ip | 拒绝中继到指定网段 | 不设变成 SSRF 跳板 |
allowed-peer-ip | 仅允许中继到指定网段 | 内网专用 TURN 必备 |
no-multicast-peers | 拒绝多播目的 | 不设可做 UDP 反射攻击 |
4. 服务端签发凭据:Node.js 实现
4.1 最小实现
// turn-credential.ts
import { createHmac, randomBytes } from 'node:crypto';
interface IceServerConfig {
urls: string[];
username: string;
credential: string;
/** 凭据失效时间,秒级 Unix timestamp */
expiresAt: number;
}
interface IssueOptions {
/** 业务用户 ID,作为审计标识 */
userId: string;
/** 房间 ID,可选,用于审计与限流 */
roomId?: string;
/** TTL 秒数,默认 600(10 分钟) */
ttlSeconds?: number;
}
const TURN_SECRET = process.env.TURN_SHARED_SECRET;
if (!TURN_SECRET || TURN_SECRET.length < 32) {
throw new Error('TURN_SHARED_SECRET 必须配置且长度 >= 32');
}
const TURN_URLS = [
'turn:turn.example.com:3478?transport=udp',
'turn:turn.example.com:3478?transport=tcp',
'turns:turn.example.com:5349?transport=tcp',
];
/**
* 生成 RFC 8489 附录 B 风格的 TURN 短期凭据。
* 与 coturn use-auth-secret + static-auth-secret 配合使用。
*/
export function issueTurnCredential(opts: IssueOptions): IceServerConfig {
const ttl = clampTtl(opts.ttlSeconds ?? 600);
const expiresAt = Math.floor(Date.now() / 1000) + ttl;
// identifier 格式:roomId.userId.随机熵
// 随机熵防止同一用户同一时刻拿到相同 username(便于按 allocation 维度做审计)
const nonce = randomBytes(4).toString('hex');
const identifier = [opts.roomId ?? 'g', opts.userId, nonce]
.map(sanitize)
.join('.');
const username = `${expiresAt}:${identifier}`;
const credential = createHmac('sha1', TURN_SECRET!)
.update(username)
.digest('base64');
return { urls: TURN_URLS, username, credential, expiresAt };
}
/** 限制 TTL 在合理范围,防止误传 0 或 24h+ */
function clampTtl(ttl: number): number {
const MIN = 60; // 至少 1 分钟
const MAX = 30 * 60; // 最多 30 分钟
return Math.min(MAX, Math.max(MIN, ttl));
}
/** username 不能含 ':'(与 timestamp 分隔符冲突)和空白 */
function sanitize(input: string): string {
return input.replace(/[^A-Za-z0-9_-]/g, '_').slice(0, 32);
}
4.2 Express 路由层
// routes/ice.ts
import type { Request, Response } from 'express';
import { issueTurnCredential } from '../turn-credential';
import { rateLimit } from '../middleware/rate-limit';
import { requireAuth } from '../middleware/auth';
// 鉴权前置:必须登录态 + JWT 校验通过
router.get(
'/api/ice-servers',
requireAuth,
rateLimit({ windowMs: 60_000, max: 20, key: (req) => req.user.id }),
(req: Request, res: Response) => {
const { roomId } = req.query;
// 业务校验:用户必须是房间成员
if (typeof roomId !== 'string' || !roomService.isMember(roomId, req.user.id)) {
return res.status(403).json({ error: 'not_in_room' });
}
const turn = issueTurnCredential({
userId: req.user.id,
roomId,
ttlSeconds: 600,
});
// STUN 走公网公开服务即可,TURN 必须短期
res.set('Cache-Control', 'no-store');
res.json({
iceServers: [
{ urls: ['stun:stun.l.google.com:19302'] },
{
urls: turn.urls,
username: turn.username,
credential: turn.credential,
},
],
expiresAt: turn.expiresAt,
// 客户端在 expiresAt - 60s 时主动刷新
refreshBeforeMs: (turn.expiresAt * 1000) - 60_000,
});
},
);
4.3 客户端续期(防止通话中凭据过期)
// client-side
class TurnCredentialRefresher {
private timer?: number;
async fetch(): Promise<RTCIceServer[]> {
const r = await fetch(`/api/ice-servers?roomId=${encodeURIComponent(roomId)}`, {
credentials: 'include',
});
if (!r.ok) throw new Error(`ice fetch failed: ${r.status}`);
const body = await r.json();
this.scheduleRefresh(body.refreshBeforeMs);
return body.iceServers;
}
private scheduleRefresh(refreshAt: number) {
const wait = Math.max(0, refreshAt - Date.now());
this.timer = window.setTimeout(async () => {
const next = await this.fetch();
// setConfiguration 只影响新 allocation,不会断现有连接
pc.setConfiguration({ iceServers: next });
}, wait);
}
}
重要:
RTCPeerConnection.setConfiguration()不会强制现有 allocation 重新鉴权。已经建立的中继路径会继续工作直到链路本身断开,凭据 TTL 只影响新建 allocation。所以 TTL 5~10 分钟对正常通话毫无影响。
5. 单租户 / 多租户隔离
5.1 单租户(最常见)
只有一个业务系统使用 TURN,配置一个 realm、一个 static-auth-secret 即可。
5.2 多租户
多个业务方共享 coturn 集群,必须做到:
- realm 隔离:每个租户一个
realm,coturn 支持多 realm 并存(realm字段写在 username 里)。 - secret 隔离:每个 realm 用独立 secret,通过
--db+ 数据库表做映射,避免单 secret 泄露污染所有租户。 - 配额隔离:用
user-quota给每个 realm 设独立上限(coturn 支持realm-options自定义)。 - 允许网段隔离:租户 A 只能中继到 SFU A 集群,用
allowed-peer-ip限定。
5.3 什么时候不适用多租户
- 业务规模不大、所有产品线由同一团队维护:单 secret + 单 realm 心智更轻。
- 强合规场景(医疗、金融):每个租户应独立部署 coturn 实例,避免共享内核被横向探测。
5.4 失败时的样子
- 租户 A 的用户能拿到租户 B 的 SFU 中继:peer-ip 没隔离。
- 一个租户被攻击导致
total-quota打满,其他租户全部新建 allocation 失败:没做 realm 级配额。
6. 防 SSRF:peer-ip 黑名单
6.1 攻击模型
TURN 的本职就是"把客户端的数据中继到任意 peer"。如果不做 peer-ip 限制,攻击者可以:
- 用合法凭据建立 allocation。
- 发送
CreatePermission/ChannelBind指向10.0.0.1:6379、169.254.169.254:80(AWS / GCP 元数据接口)。 - 通过
Send/ChannelData发任意 payload,TURN 帮他从 TURN 服务器内网视角访问目标。
这是教科书式 SSRF。AWS 元数据接口 169.254.169.254 不做鉴权,拿到的是机器 IAM 临时凭据,等于 TURN 服务器权限被盗。
6.2 必须屏蔽的网段
| 网段 | 用途 | 风险 |
|---|---|---|
0.0.0.0/8 | 保留 | 任意目的 |
10.0.0.0/8 | 私有 | 内网横向 |
100.64.0.0/10 | CGNAT | 运营商内网 |
127.0.0.0/8 | 回环 | 本机服务 |
169.254.0.0/16 | 链路本地 | 云元数据接口 |
172.16.0.0/12 | 私有 | 内网横向 |
192.168.0.0/16 | 私有 | 内网横向 |
224.0.0.0/4 | 多播 | 反射放大 |
::1/128、fc00::/7、fe80::/10 | IPv6 等价 | 同上 |
6.3 反向白名单(更安全)
如果你的 SFU 集群 IP 段固定,应该用 allowed-peer-ip 而不是黑名单:
# 仅允许中继到自家 SFU 段
allowed-peer-ip=203.0.113.20-203.0.113.40
allowed-peer-ip=2001:db8::/64
白名单的工程优势:默认拒绝、新增网段需要主动配置、不会因为漏写某个保留段被绕过。
6.4 什么时候不适用
- P2P 场景下 peer 是任意客户端公网 IP:不能上严格白名单,只能上完整黑名单 + 配额 + 监控。
- 只做内网 TURN(VPN 入口):直接
allowed-peer-ip锁死内网段即可,黑名单可省。
7. 滥用监控指标与告警
7.1 必看指标
| 指标 | 数据来源 | 告警阈值(参考) |
|---|---|---|
| TURN 入向带宽 / 出向带宽 | iptables、nftables、ifstat | 超过历史 P95 的 2 倍 |
| 同时 allocation 数 | coturn --prometheus | 超过 total-quota 的 80% |
| 单凭据并发 allocation | coturn 日志解析 | 超过 user-quota 的 80% |
| 单凭据累计流量 | 日志聚合 | 单 username 5 分钟内 > 100 MB |
| Peer IP 多样性 | 日志聚合 | 单凭据 5 分钟内 peer IP 数 > 50 |
| 地理异常 | 凭据签发日志 vs allocation 源 IP | 同凭据签发地 vs 使用地国家不一致 |
401/438 比例 | coturn 日志 | 突然上升 → secret 泄露后被遍历,或客户端时钟漂移 |
7.2 coturn Prometheus 指标
coturn 4.6+ 内置 Prometheus exporter,启用:
prometheus
prometheus-port=9641
prometheus-address=127.0.0.1
关键指标:
coturn_allocations_total{realm}:累计 allocation 数。coturn_traffic_bytes_total{realm,direction}:累计流量。coturn_sessions{realm}:当前活跃会话。
接 Grafana 即可。
7.3 业务侧审计日志
签发服务必须落库以下字段:
timestamp | userId | roomId | clientIp | userAgent | username | expiresAt
排查滥用时,用 username 反查到 userId,再看用户的登录历史、设备指纹。如果 username 在 coturn 日志里出现但签发库里没有记录,立即换 secret——这意味着 secret 已经被人独立计算了。
8. 流量配额与计费上限
8.1 三级护栏
单会话上限 (max-bps) → 单用户上限 (user-quota * max-bps) → 全服务上限 (bps-capacity)
计算示例:720p 视频会议,单流约 1.5 Mbps,给点冗余设 max-bps=2000000。一个用户最多 12 个 allocation,理论峰值 24 Mbps;服务端 200 Mbps 容量可承受 8 个并发重度用户或上百个轻度用户。
8.2 云账单兜底
仅靠 coturn 配置不够,云厂商侧也要做:
- 流量阈值告警:CloudWatch、阿里云监控按小时阈值发报警。
- 网卡限速:宿主机
tc qdisc或云厂商网卡 QoS,硬上限不依赖 coturn 进程是否健康。 - 预算硬熔断:超过日预算自动停机或切换只读 IAM。
8.3 失败时的样子
- 凌晨突然出现持续 24h 的入向带宽 100 Mbps 跑满,业务侧无对应房间数据:被刷流量了,立即换 secret + 临时降
bps-capacity。 - coturn 进程内存暴涨:
total-quota没设或设得过大,allocation 表撑爆。
9. nginx 反向代理与签发接口速率限制
TURN 协议本身不能走 HTTP 反代(它是 UDP/TCP 上的二进制协议),但签发凭据的 HTTP 接口必须受控。
# /etc/nginx/conf.d/ice-servers.conf
# 按用户身份限速:每分钟 20 次(凭据 10 分钟有效,正常用户没必要更频繁)
limit_req_zone $http_authorization zone=ice_per_user:10m rate=20r/m;
# 按 IP 兜底:未登录尝试每分钟 10 次
limit_req_zone $binary_remote_addr zone=ice_per_ip:10m rate=10r/m;
server {
listen 443 ssl http2;
server_name api.example.com;
ssl_certificate /etc/letsencrypt/live/api.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/api.example.com/privkey.pem;
# 凭据签发接口
location = /api/ice-servers {
limit_req zone=ice_per_user burst=10 nodelay;
limit_req zone=ice_per_ip burst=5 nodelay;
limit_req_status 429;
# 强制不缓存
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header Host $host;
proxy_pass http://signaling_upstream;
proxy_hide_header Cache-Control;
add_header Cache-Control "no-store" always;
}
# 防爬:未带 Authorization 直接 401
if ($http_authorization = "") {
return 401;
}
}
要点:
- 限速维度优先
Authorization(用户级),其次 IP,避免 NAT 后大量真实用户互相影响。 - 必须
Cache-Control: no-store,否则 CDN 缓存会把同一份凭据下发给多个用户。 - 接口路径不要太"显眼"(避免用
/turn、/credentials),降低被自动化扫描的概率。
10. 日志与告警实操
10.1 关键日志模式
| 日志 | 可疑信号 | 处置 |
|---|---|---|
401 突增 | secret 泄露被遍历 / 时钟漂移 | 换 secret,检查 NTP |
403 Forbidden Peer IP | 有人尝试 SSRF | 留证、封 IP、查 username 反查用户 |
Total quota reached | 接近 OOM | 扩容 + 临时降 total-quota |
Allocation refresh 高频 | 客户端实现错或攻击 | 看单 username 频率 |
| 单 username 出现在 > 5 个源 IP | 凭据被分发滥用 | 立即吊销该用户、换 secret |
10.2 自动化告警示例(PromQL)
# 5 分钟内单 realm 流量突增 3 倍
(
rate(coturn_traffic_bytes_total[5m])
/
rate(coturn_traffic_bytes_total[1h] offset 1h)
) > 3
# allocation 数接近上限
coturn_allocations_total / 1200 > 0.8
# 401 比例异常
rate(coturn_auth_failures_total[5m])
/ ignoring(reason) rate(coturn_auth_attempts_total[5m]) > 0.1
10.3 应急 runbook
发现疑似滥用:
- 立即在 nginx 层限制凭据签发接口(rate=1r/m)。
- 在 coturn 配置里把
bps-capacity临时降到日常的 30%。 - 旋转
static-auth-secret,reload coturn(已有 allocation 不受影响,新建会被拒)。 - 业务侧同步推送客户端"凭据失效,请重新加入"的兜底信令。
- 复盘签发日志:找出第一个异常 username,定位泄露点(前端、日志、CDN 缓存)。
11. 反模式
| 反模式 | 后果 | 正确做法 |
|---|---|---|
把 static-auth-secret 写进客户端 | 任何人都能签凭据,TURN 完全失守 | secret 只在后端,客户端只拿签好的 username/credential |
| 用永久 username/password | 一次泄露永久滥用 | REST 短期凭据,TTL 5~10 分钟 |
不设 denied-peer-ip / allowed-peer-ip | TURN 变 SSRF 跳板,云元数据被盗 | 默认全黑名单 + 业务 SFU 白名单 |
不设 user-quota | 单凭据可开无限 allocation 打爆服务器 | user-quota=12 起步,按业务调 |
不设 bps-capacity / max-bps | 一次刷流量打爆账单 | 配置三级带宽护栏 + 云侧硬限速 |
| 签发接口无鉴权 | 爬虫即可批量取凭据 | 必须登录态 + 限速 + 房间成员校验 |
Cache-Control 没设 no-store | CDN 把同一份凭据下发给多个用户 | 显式 no-store |
| TTL 设置太长(>1 小时) | 泄露窗口扩大、吊销不及时 | 5~10 分钟,客户端自动续期 |
| 凭据日志只记 username 不记 userId | 出事无法溯源 | username 内嵌 userId + 业务侧落库审计 |
| 把 STUN 和 TURN 混用同一域名同一端口、同一凭据策略 | STUN 不需要鉴权,混用让攻击面更乱 | STUN 用公开服务或独立配置,TURN 单独走鉴权 |
| 多租户共用单 realm 单 secret | 一个租户被打,所有租户连坐 | realm 隔离 + secret 隔离 + 配额隔离 |
用 coturn 默认配置直接上线 | 默认开启了一堆调试端口、CLI 口 | no-cli、关 telnet、关 RFC5780 |
12. 上线前自查清单
-
static-auth-secret长度 ≥ 64,仅在后端环境变量 - 客户端通过 HTTPS 鉴权接口拿凭据,TTL 10 分钟以内
- coturn 启用
use-auth-secret+lt-cred-mech -
denied-peer-ip覆盖所有内网段、169.254.0.0/16、IPv6 等价段 - 业务 SFU 段已在
allowed-peer-ip(如适用) -
user-quota、total-quota、bps-capacity、max-bps全部设值 - coturn
--prometheus接入监控,配置 5 类核心告警 - 签发接口走 nginx 限速,user 级 + IP 级双维度
- 签发日志落库:userId / roomId / username / clientIp / expiresAt
- 应急 runbook 写好,secret 旋转剧本演练过一次
- 云账单告警 + 网卡 QoS 硬上限
-
no-cli、no-loopback-peers、no-multicast-peers、no-rfc5780全部启用
13. 权威资料
- RFC 8489 Session Traversal Utilities for NAT (STUN),附录 B 描述 REST 短期凭据机制:https://www.rfc-editor.org/rfc/rfc8489
- RFC 8656 Traversal Using Relays around NAT (TURN):https://www.rfc-editor.org/rfc/rfc8656
- RFC 7635 Session Traversal Utilities for NAT (STUN) Extension for Third-Party Authorization(OAuth 化扩展,多租户进阶):https://www.rfc-editor.org/rfc/rfc7635
- coturn 官方仓库与 wiki:https://github.com/coturn/coturn
- coturn auth-secret 文档:https://github.com/coturn/coturn/wiki/turnserver#shared-secret
- coturn turnserver.conf 全量参数:https://github.com/coturn/coturn/blob/master/examples/etc/turnserver.conf
- OWASP SSRF 防御速查:https://cheatsheetseries.owasp.org/cheatsheets/Server_Side_Request_Forgery_Prevention_Cheat_Sheet.html
- IANA Special-Purpose Address Registry(peer-ip 黑名单依据):https://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml
- 核对日期:2026-06-22