Appearance
简单鉴权
本文档定义基于secret的轻量级鉴权方案,适用于MQTT over TLS环境。
设计目标
- 极简实现:设备端仅需字符串比对
- 零依赖:不需要加密库
- 易调试:请求内容明文可读
前置条件
- MQTT必须使用TLS:secret明文传输,依赖TLS保护
- 设备已持有secret:通过GATT配网获得
请求格式
在MessagePending基础上增加secret字段:
json
{
"sender": "app-001",
"receiver": "device-001",
"msg_id": "550e8400-e29b-41d4-a716-446655440000",
"action": "unlock",
"time": 1700000000,
"exp": 1700000060,
"secret": "550e8400-e29b-41d4-a716-446655440000",
"value": {"door": "front"}
}| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| secret | string | 是 | 设备凭证,配网时获得 |
验证流程
验证步骤
- 验证secret:与本地存储的secret比对
- 防重放:检查msg_id是否已处理过
- 检查过期:当前时间是否超过exp
- 缓存msg_id:记录已处理的msg_id(用于防重放)
防重放机制
设备端维护已处理msg_id缓存:
- 缓存内容:msg_id + exp
- 缓存清理:定期清理已过期的msg_id
- 内存估算:按60秒过期、每秒1请求,约60个UUID ≈ 2KB
c
// 伪代码
typedef struct {
char msg_id[37];
int64_t exp;
} processed_msg_t;
processed_msg_t msg_cache[MAX_CACHE_SIZE];
bool is_duplicate(const char* msg_id) {
for (int i = 0; i < cache_count; i++) {
if (strcmp(msg_cache[i].msg_id, msg_id) == 0) {
return true;
}
}
return false;
}响应格式
成功:按业务返回MessageComplete
鉴权失败:
json
{
"msg_id": "550e8400-e29b-41d4-a716-446655440000",
"error": "invalid_secret"
}重复请求:
json
{
"msg_id": "550e8400-e29b-41d4-a716-446655440000",
"error": "duplicate_request"
}请求过期:
json
{
"msg_id": "550e8400-e29b-41d4-a716-446655440000",
"error": "request_expired"
}实现示例
C语言(设备端)
c
#include <string.h>
#include <stdbool.h>
#include <time.h>
// 设备存储的secret
static char device_secret[37];
// 验证请求
typedef enum {
AUTH_OK,
AUTH_INVALID_SECRET,
AUTH_DUPLICATE,
AUTH_EXPIRED
} auth_result_t;
auth_result_t verify_request(const char* secret, const char* msg_id, int64_t exp) {
// 1. 验证secret
if (strcmp(secret, device_secret) != 0) {
return AUTH_INVALID_SECRET;
}
// 2. 检查重复
if (is_duplicate(msg_id)) {
return AUTH_DUPLICATE;
}
// 3. 检查过期
if (time(NULL) > exp) {
return AUTH_EXPIRED;
}
// 4. 缓存msg_id
cache_msg_id(msg_id, exp);
return AUTH_OK;
}JavaScript(APP端)
javascript
function buildRequest(action, value, secret) {
const now = Math.floor(Date.now() / 1000)
return {
sender: 'app-001',
receiver: 'device-001',
msg_id: crypto.randomUUID(),
action: action,
time: now,
exp: now + 60, // 60秒后过期
secret: secret,
value: value
}
}
// 使用示例
const request = buildRequest('unlock', { door: 'front' }, '550e8400-...')
mqttClient.publish('nodes/device-001/pending', JSON.stringify(request))安全性说明
| 威胁 | 防护方式 |
|---|---|
| 窃听secret | TLS加密传输 |
| 伪造请求 | secret验证 |
| 重放攻击 | msg_id去重 + exp过期 |
| 内容篡改 | TLS完整性保护 |
重要
本方案必须在TLS环境下使用,否则secret将明文暴露!
与HMAC方案对比
| 简单鉴权 | HMAC签名 | |
|---|---|---|
| 实现复杂度 | 极低 | 中 |
| 依赖库 | 无 | 需要HMAC |
| 调试难度 | 低 | 中 |
| 离线安全 | ❌ 依赖TLS | ✅ |
| 适用场景 | MQTT over TLS | 任意传输 |
