跳到主要内容
版本:开发版

设备凭据

设备的身份是稳定的令牌。凭据是认证时提交的材料,与身份分开保存。设备可以持有多个凭据并轮换,而无需改变身份。

状态

已提供。通过控制台设备详情页的**凭据(Credentials)**选项卡,或 device-management GraphQL API 管理。

凭据类型​

类型设备提交内容保存的密钥
ACCESS_TOKENbearer 令牌(凭据 ID)令牌带密钥的单向摘要
MQTT_BASIC用户名(凭据 ID)+ 密码密码带密钥的单向摘要
X509_CERTIFICATETLS 客户端证书,配合 MQTT 用户名 {tenant}:@x509,无密码没有任何机密:证书的 SHA-256 就是固定值,证书本身是公开的
X.509 凭据

X509_CERTIFICATE 凭据会指定其证书所通向的信任锚。创建凭据时平台会验证证书链,并把凭据 id 设为证书的 SHA-256 pin(它是公开的)。一台设备最多持有三个,没有信任锚的证书凭据不会通过认证。目前证书能做什么,参见目前证书方面可用的功能。完整流程见使用证书连接设备和证书身份。

读取凭据需要 device:write​

密钥是只写的,两种类型都有:MQTT_BASIC 凭据的密码,以及 ACCESS_TOKEN 凭据的令牌。注册凭据时提交它,平台只保存其带密钥的单向摘要,从不保存密钥本身。读取时 API 对 credentialValue 一律返回 null。访问令牌就是 credentialId,API 只返回它一次:在设置它的那次调用的响应中(注册凭据、轮换令牌的更新,或生成令牌的设备更换)。此后的每次读取都改为返回一个指纹,即以 dctk1$ 开头的值,它能标识该凭据,但不能当作令牌提交。控制台在你设置密码或令牌后只显示一次,此后不再显示。参见密码与令牌只显示一次,以及轮换。

因此,无论类型,读取设备凭据都不会提供以该设备身份认证所需的内容。你仍然可以按令牌查找凭据:deviceCredentials 接受令牌或指纹作为条件中的 credentialId,平台会先计算你输入的令牌的摘要再进行匹配。

所有返回凭据的查询仍然需要 device:write,而不是 device:read,因为列表会显示设备的用户名、到期时间和状态。只读用户不能列出设备凭据,所以控制台不向其显示**凭据(Credentials)**选项卡。这不会减少 device:write 持有者的能力,他们本来就能为租户任何设备注册凭据。它的作用是让这份列表不进入所有启用租户成员都获得的只读基础权限。

设备如何提交凭据​

通过 HTTP 时,凭据在事件体中携带(参见连接设备)。通过 MQTT 时,凭据用于认证连接,事件体不携带任何凭据:平台 MQTT 消息代理上包含 credentialType、credentialId 或 credentialSecret 的事件会被拒绝。HTTP 的形式是:

{
"device": "sensor-001",
"credentialType": "ACCESS_TOKEN",
"credentialId": "5f989616-2a0d-4160-8ae1-da5fad2898b2",
"eventType": "Measurement",
"payload": { "entries": [ { "measurements": { "temperature": "21.5" } } ] }
}

MQTT_BASIC 还携带 "credentialSecret": "<password>"。这些字段仅用于 HTTP。

平台将凭据解析到所属设备并验证。验证遵守凭据有效期,禁用凭据即撤销。实例设备认证模式决定执行严格程度:

  • disabled:信任自行声明的 device 令牌,不需要凭据。
  • optional:提交凭据时以凭据为准,没有凭据时信任设备令牌。
  • required:没有有效凭据就拒绝事件。这是默认值。

认证成功后,以凭据解析出的设备为准。事件 device 令牌若指定另一台设备,会被拒绝,因此已认证设备无法冒充其他设备。

撤销多久生效​

每个 device-management 副本将刚验证过的凭据在内存保留,默认最多五分钟(inMemoryCache.ttlSeconds,1 到 3600 秒),使后续事件无需再次读数据库。内存副本与存储凭据采用完全相同检查:每个事件都比较 MQTT_BASIC 密码,有效期在对应时刻生效。验证失败的凭据绝不缓存,因此修正后下一事件即可生效。

禁用、删除或修改凭据,或替换、编辑、删除设备时,执行变更的副本在回应前删除自己的缓存,然后通知其他副本删除。因此,撤销通常在设备下一事件生效。副本与消息代理的连接断开后恢复时,会丢弃它持有的全部缓存,因为它可能错过了这类通知。如果通知在没有断开连接的情况下丢失,或升级时部分副本仍运行较早版本,被撤销凭据仍可能在该副本上认证事件,直到其缓存过期:默认最长为变更后五分钟。如果这对你来说太长,请调低 inMemoryCache.ttlSeconds;此后凡是凭据不在内存中的事件都会读取数据库。

连接不受此缓存影响。每次 MQTT 连接,不论使用密码还是访问令牌,都检查数据库,因此被撤销凭据不能在任何副本建立新连接。清除已删除租户的数据时,每个副本都会丢弃该租户的缓存。

设备出示的凭据在平台内部传递时同样不以明文保存。随事件到达的密码或访问令牌,在事件写入平台内部事件流之前即被密封;为诊断而保留的消息副本(例如无法解码的消息)会去除其中的凭据。

两层检查:连接与事件​

事件体凭据用于逐事件检查。MQTT 和 NATS 连接也在消息代理本身认证:

  • TLS。 MQTT 和 NATS 监听器使用 TLS,设备通过实例 CA 建立 TLS 连接。
  • Auth-callout。 消息代理要求平台批准每个新连接(NATS auth-callout)。平台认证连接,并将其限定在该设备的主题,而非整个租户主题。设备只能发布自己的事件、读取自己的命令,不能做其他事。MQTT_BASIC 设备使用 MQTT 用户名 {tenant}:{credentialId} 和凭据密码连接,与认证事件的凭据相同。无法认证的设备连连接都无法建立。
  • 客户端 ID。 连接还必须提供 MQTT 客户端 ID {instanceId}:{tenant}:{deviceToken}。可以加 : 和设备自选后缀,例如 {instanceId}:{tenant}:{deviceToken}:cmd。后缀允许第二个并发会话不踢掉第一个,例如一条连接发布,另一条订阅命令。不是以设备自身 {instanceId}:{tenant}:{deviceToken} 开头的 ID 会被拒绝。消息代理按客户端 ID 保存会话,自由选择会让设备接管另一设备会话。

传输详情见连接设备。

停用设备​

停用设备会将其拒之门外,但不会删除设备,也不会改动它的凭据。可用于隔离已被入侵或正在向平台大量发送数据的设备,修复后再重新启用。

设备停用期间:

  • 其事件被丢弃。 无论通过哪个凭据或传输到达,事件在到达时即被拒绝,并计入 devicechain_devicemanagement_resolve_device_disabled_total。这些事件既不存储,也不进入死信,设备重新启用后也不会恢复。连接状态是例外,因为它由平台而不是设备报告:设备已有的连接结束时,设备会显示为已断开,该变化会像其他连接变化一样保留在其事件历史中。这不会触发任何告警。Sparkplug 设备在停用期间也可能显示为已连接,因为其边缘节点会代为报告它的连接。
  • 无法建立新连接。 消息代理拒绝连接,答复与密码错误时相同;LwM2M 设备的注册会被以 4.03 Forbidden 拒绝。设备已有的连接会保持到结束,但它在该连接上发布的所有内容都会被丢弃或拒绝:包括它的事件、配置请求与报告,以及更新报告与下载请求。平台也不再通过 LwM2M 读取它的设备信息。
  • 不会向它发送新的配置或更新,启用后会为它补发。 设备停用期间生成的配置修订会被保留但不会发送,也不会向它提供更新。启用设备时,平台会向它发送最新的配置修订,并重新提供它当前的更新,因此它通常不必等到下一次重启或同步。由于停用期间它被拒绝,它那时发起的同步没有得到答复;设备不会收到任何通知。
  • 发往它的命令被拒绝,代码为 DEVICE_DISABLED。批量(设备群)命令会将其列为被拒绝的设备。参见入队被拒绝时。在设备被停用之前已为其排队的命令会被保留:既不发送,也不取消。重新启用设备后,它们按从旧到新的顺序发出;等待期间到达各自的过期时间则会过期。控制台中设备的 Commands 选项卡会显示被保留的命令数量,并提供 Cancel all 操作,参见为已停用设备保留的命令。保留在停用后的瞬间生效,因此恰在此刻发布的命令仍会送达设备;已发送的命令已在设备上,无法召回。
  • 不会触发静默告警。 在设备停止上报时触发告警的规则会忽略已停用的设备,而且设备被停用时,它此前已触发的静默告警会被清除。设备重新启用后,规则会从那一刻起等待一个完整的静默窗口,之后才可能将其判定为静默。
  • 其余一切保留:数据、凭据、配置、关系和分组成员身份。

变更在设备的下一个事件时于所有副本生效:这是对设备的变更,因此会像撤销一样丢弃缓存副本。

在控制台中,打开设备并选择停用(Disable)(或启用(Enable));已停用的设备会在其页面和设备列表中显示已停用徽章。通过 GraphQL,在部分更新中设置 enabled。null 会被拒绝;停用一个已停用的设备时,会保留它首次被停用的时间,由 disabledAt 报告:

mutation {
updateDevice(token: "pump-7", request: { enabled: false }) {
token
enabled
disabledAt
}
}

也可以在 createDevice 中传入 enabled: false,直接创建一个已停用的设备。该变更会作为设备的一次更新记录在审计日志中。

重复连接失败会被延缓​

反复失败后,提供用户名和密码的 MQTT 连接会被延缓,使密码不能按代理接受连接的速率被猜测。

  • **计数范围:**一个 MQTT 用户名({tenant}:{credentialId})的连续密码连接失败。未知用户名与真实用户名同样计数、同样拒绝,答案不泄露哪些用户名存在。拒绝耗时接近但不完全相同:存在的用户名需要额外读取一条数据库行,未知用户名不需要。该行只保存检查必需内容,因此差异不会随设备元数据、名称或描述大小增加。
  • **时间安排:**前 10 次连续失败不延缓。第 10 次后,下一次尝试等待 1 秒。每次后续失败将等待翻倍,最多 30 秒。
  • 等待期间,正确密码也被拒绝。 消息代理给出与错误密码相同的拒绝。等待结束后正常连接。
  • 成功连接重置计数。
  • **不延缓:**访问令牌连接(无密码连接),以及事件体中的凭据。逐事件检查不向发送者回应,因此无法用于猜测。
知道设备用户名的人可以延迟其重连

计数按用户名,而用户名不是秘密。持续发送错误密码的人可以持续阻止该设备重连:每次等待最多 30 秒,但结束后可以立即开始下一次。已连接设备在重连前不受影响。不要公开设备用户名,并为每个 MQTT_BASIC 凭据使用强密码。

计数保存在消息代理持久存储 JetStream 中,因此每个 device-management 副本看到相同计数。

  • JetStream 不可达时,密码连接被拒绝,直到恢复,因为无法计数的连接不会完成检查。这包括保存计数的存储桶 JetStream 领导者变化的短暂窗口,例如 NATS 节点重启。访问令牌连接仍可用。
  • 保存计数的存储桶有容量限制。 每次连接,无论成功与否,都保留条目十分钟。存储桶满时,连接继续工作,但不再延缓,触发 DeviceCredentialAttemptStoreFull 告警。大量不同用户名连接,或极大设备群同时重连时会发生。十分钟后条目自行消失。需要更多空间时,提高 instance.config.infrastructure.nats.kvStateMaxBytes。

devicechain_devicemanagement_credential_checks_total 按 outcome 统计每次密码连接检查,结果包括:

  • throttled:等待期间拒绝连接。
  • unavailable:无法访问计数。
  • store_full:存储桶满。

注册凭据(控制台)​

  1. 打开设备详情页,选择**凭据(Credentials)**选项卡。
  2. 选择凭据类型并填写对应字段:生成或粘贴访问令牌、输入 MQTT-basic 用户名和密码。密码可以自己输入,也可以点击生成强密码(Generate strong password)。
  3. 点击添加凭据(Add credential)。
  4. 页面随即显示一个带复制按钮的提示框,内含密码(MQTT_BASIC)或访问令牌(ACCESS_TOKEN)。请立即复制:这是它唯一一次显示。复制好后点击完成(Done)。离开页面、刷新页面或打开另一台设备也会清除该提示框。

在对应行删除凭据。之后设备无法再使用它认证。

密码与令牌只显示一次,以及轮换​

由于平台只保存 MQTT_BASIC 密码和 ACCESS_TOKEN 令牌的摘要,任何人(包括管理员)都无法读回二者。控制台据此设计:

  • 密码或令牌只显示一次:在添加凭据或设置新的密码或令牌时,通过带复制按钮的提示框显示。此后凭据所在行只表明已设置密码或访问令牌。任何界面都不会显示已保存的密钥或其摘要,任何表单也不会预填密钥。
  • 丢失的密码或令牌无法找回,请设置新的。
  • 轮换密码:点击凭据所在行的设置新密码(Set new password),输入密码或点击生成强密码,再点击保存密码(Save password)。新密码同样只显示一次。保存后,设备当前的密码立即无法建立新连接;对事件而言,它与撤销一样失效,默认在几分钟之内(参见撤销多久生效)。请先准备好把新密码装入设备。凭据的其他字段保持不变。
  • 轮换访问令牌:点击凭据所在行的**设置新令牌(Set new token)**并确认。控制台会生成新令牌并只显示一次。确认后,设备当前的令牌立即无法建立新连接;对事件而言,它与撤销一样失效。通过 GraphQL,只用 credentialId 调用 updateDeviceCredential 即可。
  • 生成的密码是来自浏览器安全随机源的 24 个随机字节,以 URL 安全的 base64 书写(32 个字符)。
  • 长度:平台拒绝短于 16 字节的 MQTT_BASIC 密码。控制台在你输入时给出提示,按 UTF-8 字节而非字符计数(ASCII 之外的字符占 2 至 4 字节),并仍会发送你输入的内容,因此二者不一致时你看到的是平台自己的拒绝信息。

MQTT_BASIC 的用户名是标识符而不是密钥,仍会列出并带有复制按钮。

注册凭据(GraphQL)​

mutation {
createDeviceCredential(request: {
token: "b2e1…", # a fresh unique credential token
deviceToken: "sensor-001",
credentialType: "ACCESS_TOKEN",
credentialId: "5f989616-2a0d-4160-8ae1-da5fad2898b2",
enabled: true
}) { id token credentialType credentialId enabled }
}

对于 ACCESS_TOKEN,响应中的 credentialId 就是你发送的令牌,也是此后任何响应最后一次包含它。令牌最长 256 字节,只以带密钥的摘要保存,因此完全按提交内容逐字节校验。要更换令牌,只用 credentialId 调用 updateDeviceCredential:旧令牌立即失效,响应中带有新令牌。将现有凭据改为 ACCESS_TOKEN 或从 ACCESS_TOKEN 改为其他类型时,必须在同一请求中提供 credentialId(否则返回 INVALID_VALUE),因为保存的 ID 在一种类型下表示令牌,在另一种类型下表示用户名。

对于 MQTT_BASIC,还要传入 credentialValue: "<password>"(只写)。密码完全按提交内容校验,包括前后空格,因此必须与设备提交内容逐字节一致;空值表示不保存密码。密码长度必须至少为 16 字节(最多 4096 字节);更短的密码会被拒绝,并返回 extensions.code 为 INVALID_VALUE 的错误,注册凭据和修改密码时均如此。在设置此最小长度之前创建的密码在你修改它之前仍然有效。将现有凭据改为 MQTT_BASIC 时,必须在同一请求中提供 credentialValue。之后要更改,只需用 credentialValue 调用 updateDeviceCredential:其他字段保持不变,旧密码立即失效。两个调用都不会返回密码。注册凭据和列出设备凭据都需要 device:write 权限,参见读取凭据需要 device:write。