4、基于Token的设备身份认证

好,我们进入第四章。前面几章聊了证书和预共享密钥,这两种方式各有各的适用场景。但说实话,在物联网设备量级上来之后,你会发现管理证书和密钥的成本越来越高。这时候,Token认证就成了一种非常灵活的选择。

我个人习惯把Token认证看作是「一次验证,多次使用」的模式。设备只需要在首次连接时完成身份校验,拿到一个Token,后续通信就靠这个Token来证明身份。你想想看,这比每次连接都做一次完整的TLS握手或者PSK校验要轻量得多。

Token认证原理

Token认证的核心思想其实很简单:设备不再需要每次都携带用户名密码或者证书,而是携带一个由认证服务器颁发的「通行证」。这个通行证里包含了设备的身份信息、权限范围、有效期等关键数据。

整个流程大致是这样的:

  1. 设备向认证服务器发起认证请求,提交自己的身份凭证(比如设备ID+密钥,或者证书)。
  2. 认证服务器验证通过后,生成一个Token返回给设备。
  3. 设备拿着这个Token去连接MQTT Broker。
  4. Broker验证Token的有效性,验证通过则允许连接。

关键点: Broker本身不需要存储设备的认证信息,它只需要验证Token的签名和有效期即可。这大大减轻了Broker的负担,也方便了认证服务的集中管理。

我在项目中遇到过一种情况:设备数量从几千台突然增长到几十万台,原来的证书管理方式完全撑不住了。后来切换到Token认证,认证服务器的压力虽然增加了,但Broker集群的负载反而下降了,因为Token验证比TLS握手快得多。

JWT在MQTT中的应用

说到Token,就不得不提JWT(JSON Web Token)。JWT是目前最主流的Token格式,它本身就是一段JSON数据,经过签名后变得不可篡改。

一个标准的JWT由三部分组成:

  • Header:声明签名算法和Token类型
  • Payload:存放实际的数据,比如设备ID、角色、过期时间等
  • Signature:对前两部分进行签名,防止篡改

在MQTT场景下,JWT的Payload里通常会包含这些字段:

字段 说明 示例值
sub 设备唯一标识 device:sn:ABC123
iss 签发者 auth-server-01
iat 签发时间 1700000000
exp 过期时间 1700086400
scope 权限范围 sensor:read actuator:write

设备连接MQTT时,把JWT放在CONNECT报文的Password字段里。Broker收到后,用预先配置的公钥验证签名,再检查exp字段是否过期。全部通过,就允许连接。

一个小技巧: 我建议把JWT放在Password字段,而不是Username字段。因为很多MQTT客户端库对Username字段有长度限制,而JWT通常比较长。放在Password字段更稳妥。

下面是一个JWT生成的示例代码(Node.js):

const jwt = require('jsonwebtoken');

const payload = {
  sub: 'device:sn:ABC123',
  iss: 'auth-server-01',
  iat: Math.floor(Date.now() / 1000),
  exp: Math.floor(Date.now() / 1000) + 3600, // 1小时后过期
  scope: 'sensor:read actuator:write'
};

const secret = 'your-256-bit-secret';
const token = jwt.sign(payload, secret, { algorithm: 'HS256' });

console.log('Generated JWT:', token);

Broker端验证JWT的代码也很简单:

const jwt = require('jsonwebtoken');

try {
  const decoded = jwt.verify(token, secret, { algorithms: ['HS256'] });
  console.log('Token valid, device:', decoded.sub);
  // 允许连接
} catch (err) {
  console.log('Token invalid:', err.message);
  // 拒绝连接
}

注意: 使用对称签名(HS256)时,Broker和认证服务器必须共享同一个密钥。如果密钥泄露,攻击者可以伪造任意Token。生产环境中,我强烈建议使用非对称签名(RS256或ES256),Broker只保存公钥,认证服务器保存私钥。

为了让你更直观地理解整个流程,我画了一张图:

JWT Token认证流程 设备 认证服务器 MQTT Broker 1. 提交凭证 2. 返回JWT 3. 携带JWT连接 验证签名+有效期 4. 连接成功/失败 说明: 设备只需在首次连接时与认证服务器交互,后续连接直接使用JWT。 Broker不存储设备凭证,只验证JWT签名,适合大规模设备场景。

Token生命周期管理

Token不是永不过期的。你想想看,如果Token永远有效,那一旦泄露,攻击者就能永久冒充设备。所以,Token的生命周期管理是认证方案中至关重要的一环。

我一般把Token的生命周期分为三个阶段:

  • 签发阶段:设备首次认证通过后,服务器生成Token并返回。这个阶段要设置合理的过期时间。
  • 使用阶段:设备携带Token连接Broker,Broker验证Token有效性。这个阶段要处理Token刷新和续期。
  • 吊销阶段:设备被移除或Token泄露,需要立即吊销Token。这个阶段需要黑名单机制。

过期时间设置

过期时间(exp)的设置是个权衡。时间太短,设备需要频繁刷新Token,增加网络开销。时间太长,安全风险增大。

我的经验是:

  • 固定设备:比如传感器、网关,建议设置24-72小时。这些设备网络稳定,可以定期刷新。
  • 移动设备:比如手持终端、车载设备,建议设置4-8小时。这些设备可能频繁断连,太短的过期时间会导致用户体验下降。
  • 临时设备:比如调试工具、一次性设备,建议设置1-2小时。用完即废,不留隐患。

一个实用建议: 我习惯在Token里加一个nbf(Not Before)字段,设置一个未来的生效时间。这样可以在设备离线时提前签发Token,设备上线后直接使用,避免在线等待。

Token刷新机制

Token过期后,设备不能直接重新认证吗?当然可以。但频繁的重新认证会增加认证服务器的压力。更好的做法是使用Refresh Token机制。

Refresh Token是一个长期有效的Token,专门用来换取新的Access Token。设备在Access Token过期前,用Refresh Token向认证服务器申请新的Access Token,整个过程对用户无感。

具体流程:

  1. 设备使用Refresh Token请求新的Access Token。
  2. 认证服务器验证Refresh Token的有效性。
  3. 验证通过,返回新的Access Token(和新的Refresh Token)。
  4. 设备用新的Access Token继续连接Broker。

注意: Refresh Token的存储必须安全。我曾经见过一个项目,把Refresh Token明文存储在设备的文件系统里,结果设备被物理破解后,攻击者拿到了所有Token。建议至少加密存储,或者使用硬件安全模块(HSM)。

Token吊销

Token吊销是最容易被忽视的环节。很多方案只考虑了签发和验证,却忘了「如果设备被盗怎么办?」。

Token吊销的常见方式:

  • 黑名单:Broker维护一个Token黑名单,每次验证时检查Token是否在黑名单中。缺点是黑名单会越来越大,影响性能。
  • 短有效期+主动刷新:把Token有效期设得很短(比如5分钟),设备频繁刷新。一旦设备被吊销,认证服务器不再签发新Token,设备自然失效。这种方式不需要黑名单,但刷新频率高。
  • 版本号机制:在Token里加一个版本号字段,每次吊销时递增版本号。Broker只接受最新版本的Token。这种方式效率高,但需要Broker和认证服务器同步版本号。

我个人比较推荐「短有效期+主动刷新」的方式。虽然刷新频率高一些,但实现简单,不需要维护黑名单,也不存在版本同步的问题。在物联网场景下,设备通常有固定的心跳间隔,正好可以用来刷新Token。

嗯,Token认证的核心内容就这些。记住一句话:Token是凭证,不是身份。它只是证明设备曾经通过认证,不代表设备永远可信。合理管理Token的生命周期,才能保证系统的安全性。