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);

这里有个细节我踩过坑:iatexp 一定要用 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 认证完整流程 客户端 信令服务器 Token 服务 ① 请求 Token(携带用户凭证) ② 转发到 Token 服务 ③ 返回 JWT Token ④ WebSocket 连接(携带 Token) 验证 Token 有效性 ⑤ 验证通过,建立连接 ⑥ 验证失败,断开连接(错误码 4001) Token 过期处理 过期触发

实际项目中的注意事项

最后分享几个我在实战中总结的经验:

  • Token 不要太大:JWT 每次请求都要传输,如果 payload 塞太多自定义字段,会拖慢 WebSocket 的握手速度。我一般控制在 200 字节以内。
  • 黑名单机制:如果用户主动登出,或者管理员封禁账号,需要让 Token 立即失效。我习惯在 Redis 里维护一个黑名单,验证时先查黑名单。
  • 日志记录:每次 Token 验证失败都要记录日志,包括时间、IP、错误原因。这能帮你快速定位攻击行为。

一个小技巧:在开发环境,可以把 Token 过期时间设成 24 小时,省得频繁刷新。但上线前一定要改成合理的短时间,并加上静默刷新机制。

嗯,Token 认证这块就聊到这儿。说白了,它就是信令服务器的守门员。守好了,整个系统就稳了一半。


公众号:蓝海资料掘金营,微信deep3321