Token 认证机制:生成 Token、验证 Token、Token 过期处理
聊到 WebRTC 信令服务器的安全性,Token 认证是我个人觉得最绕不开的一环。你想想看,信令服务器就像是一个调度中心,谁来了都得先亮个身份牌。这个身份牌,就是 Token。
我在早期做第一个 WebRTC 项目时,压根没想过加 Token。结果上线没两天,就发现有人在乱发信令消息,甚至伪造房间号。嗯,从那以后,我再也不敢省略 Token 认证了。
为什么需要 Token 认证?
说白了,WebRTC 本身只负责媒体流的加密传输,但信令通道是裸奔的。如果没有 Token 认证,任何人都可以连接你的信令服务器,随意创建房间、加入房间、甚至窃听信令内容。
Token 认证的核心作用就三个:
- 身份验证:确认客户端是谁
- 权限控制:能做什么、不能做什么
- 时效管理:过期了就失效,防止滥用
核心原则:信令服务器永远不要信任未经认证的客户端。哪怕只是内网测试,也建议加上 Token 机制,养成习惯。
Token 的生成机制
我习惯用 JWT(JSON Web Token)来做 Token。为什么选 JWT?因为它自包含、无状态、跨语言支持好。你生成一个 Token,里面就包含了用户身份、过期时间、甚至自定义的权限字段。
来看一个 Node.js 下的生成示例:
const jwt = require('jsonwebtoken');
// 生成 Token
function generateToken(userId, role) {
const payload = {
sub: userId, // 用户ID
role: role, // 角色:admin / user
iat: Math.floor(Date.now() / 1000), // 签发时间
exp: Math.floor(Date.now() / 1000) + 3600 // 1小时后过期
};
const secret = process.env.JWT_SECRET || 'your-secret-key';
return jwt.sign(payload, secret);
}
// 使用示例
const token = generateToken('user_12345', 'user');
console.log('生成的 Token:', token);
这里有个细节我踩过坑:iat 和 exp 一定要用 Unix 时间戳(秒),而不是毫秒。我曾经传了毫秒进去,结果 Token 永远不过期,吓得我赶紧改了。
我的习惯:把 JWT 的 secret 放在环境变量里,不要硬编码。另外,生产环境建议用 RSA 非对称加密,而不是 HMAC 对称加密。这样即使 secret 泄露,也能快速更换公钥。
Token 的验证流程
客户端拿到 Token 后,每次连接信令服务器都要带上它。服务器收到后,第一步就是验证 Token 的合法性。
验证逻辑其实不复杂,但有几个关键点:
function verifyToken(token) {
try {
const secret = process.env.JWT_SECRET || 'your-secret-key';
const decoded = jwt.verify(token, secret);
// 验证通过,返回解码后的 payload
return {
valid: true,
userId: decoded.sub,
role: decoded.role,
expiresAt: decoded.exp
};
} catch (error) {
// 验证失败
return {
valid: false,
error: error.message
};
}
}
// WebSocket 连接时的验证
wss.on('connection', (ws, req) => {
const token = extractTokenFromRequest(req);
const result = verifyToken(token);
if (!result.valid) {
ws.close(4001, 'Token 无效或已过期');
return;
}
// 验证通过,继续处理
console.log(`用户 ${result.userId} 连接成功`);
});
你可能会问:Token 放在哪里?我建议放在 WebSocket 连接的 URL 参数里,或者放在 HTTP 请求的 Authorization 头里。我个人更推荐后者,因为 URL 参数容易被日志记录,有泄露风险。
注意:千万不要在客户端存储 Token 的原始 secret。JWT 的 payload 是 base64 编码的,不是加密的。敏感信息(如密码)绝对不能放在 payload 里。
Token 过期处理策略
Token 过期了怎么办?直接断开连接?那用户体验太差了。我常用的策略有三种:
| 策略 | 说明 | 适用场景 |
|---|---|---|
| 静默刷新 | Token 过期前,客户端自动用 refresh token 换取新 Token | 长时间通话、直播场景 |
| 强制重连 | Token 过期后,断开连接,客户端重新获取 Token 再连 | 短连接、临时会议 |
| 滑动过期 | 每次有效请求都延长 Token 的过期时间 | 实时协作、白板共享 |
我项目中用得最多的是静默刷新。具体做法是:
// 客户端:Token 过期前 5 分钟发起刷新
setInterval(async () => {
const token = getCurrentToken();
const decoded = jwt.decode(token);
// 如果 Token 将在 5 分钟内过期
if (decoded.exp - Math.floor(Date.now() / 1000) < 300) {
const newToken = await fetchNewToken();
setCurrentToken(newToken);
// 通知信令服务器更新 Token
ws.send(JSON.stringify({
type: 'token_refresh',
token: newToken
}));
}
}, 60000); // 每分钟检查一次
服务器端收到刷新请求后,验证旧 Token 是否有效(即使已过期,只要在宽限期内),然后签发新 Token。
避坑指南:我曾经把 Token 过期时间设得太短(5分钟),结果用户频繁掉线。后来改成 1 小时过期,配合静默刷新,体验就好多了。建议根据业务场景调整:
- 视频通话:1-2 小时
- 直播推流:4-8 小时
- 临时会议:30 分钟
完整的 Token 认证流程图
下面这张图是我自己总结的,涵盖了从生成到过期的完整链路:
实际项目中的注意事项
最后分享几个我在实战中总结的经验:
- Token 不要太大:JWT 每次请求都要传输,如果 payload 塞太多自定义字段,会拖慢 WebSocket 的握手速度。我一般控制在 200 字节以内。
- 黑名单机制:如果用户主动登出,或者管理员封禁账号,需要让 Token 立即失效。我习惯在 Redis 里维护一个黑名单,验证时先查黑名单。
- 日志记录:每次 Token 验证失败都要记录日志,包括时间、IP、错误原因。这能帮你快速定位攻击行为。
一个小技巧:在开发环境,可以把 Token 过期时间设成 24 小时,省得频繁刷新。但上线前一定要改成合理的短时间,并加上静默刷新机制。
嗯,Token 认证这块就聊到这儿。说白了,它就是信令服务器的守门员。守好了,整个系统就稳了一半。
公众号:蓝海资料掘金营,微信deep3321