身份验证是 MQTT 中的传输和应用程序级安全的一部分。在传输层面上,TLS 可以使用客户端证书向服务器保证客户端的真实性,通过验证服务器证书向客户端保证服务器的真实性。在应用程序层面上,MQTT 协议提供了用户名/密码身份验证。
开发人员可以使用多种方法来确保向代理注册了正确的设备。选择正确的方法取决于解决方案所需的安全性,以及在设备上运行所需方法的能力。
以下各节将介绍其中一些方法。在代码示例中使用 Eclipse Paho 作为 MQTT 客户端库。
使用用户名和密码执行身份验证MQTT 协议在 CONNECT 消息中提供了 username和 password
字段来执行设备身份验证。客户端在连接 MQTT 代理时必须发送用户名和密码。
用户名是一个 UTF-8 编码的字符串,密码是二进制数据。它们最大为 65535 字节。MQTT 协议不会加密用户名或密码,除非使用了传输层加密,否则会以明文格式发送它们。
清单 1. 用户名和密码字段
try { MqttClient securedClient = new MqttClient(broker, clientId, persistence); MqttConnectOptions connOpts = new MqttConnectOptions(); connOpts.setCleanSession(true); connOpts.setUserName(userName); connOpts.setPassword(password.toCharArray()); System.out.println("Connecting to broker: "+broker); securedClient.connect(connOpts); System.out.println("Connected"); } catch(MqttException me) { System.out.println("reason "+me.getReasonCode()); System.out.println("msg "+me.getMessage()); System.out.println("loc "+me.getLocalizedMessage()); System.out.println("cause "+me.getCause()); System.out.println("excep "+me); me.printStackTrace(); } |
如果客户端成功获取了访问令牌,那么可以在 CONNECT 消息中通过使用 password
字段将令牌发送给代理。然后,用户名可以是一个用于识别访问令牌的特殊字符串。MQTT 中的密码的大小限制为 65535 字节,所以令牌长度不能超过这个限制。
代理可以使用令牌来执行各种验证,比如:
- 检查来自令牌的签名的有效性
- 检查令牌的有效期是否已过期
- 检查授权服务器来查看令牌是否被撤销
在设备连接到 MQTT 代理时使用的验证与应用程序发布或订阅时使用的验证相同。但是,在发布或订阅时,代理还必须为应用程序授权。此授权可通过两种方式完成:
- 令牌在范围声明中包含客户端的授权。
- 令牌有一个第三方来源,比如数据库或 LDAP,用于查找客户端的授权。
IBM Watson IoT Platform 应用程序可通过应用程序 ID、密钥和令牌来执行身份验证。IoT 应用程序密钥和令牌可在应用程序注册期间生成,可在它连接到 IBM Watson IoT Platform 时使用,如下面的示例所示:
清单 2. 应用程序密钥和令牌
123456789101112131415161718 | App properties # A unique id you choose it by yourself, maybe, abcdefg123456 appid=< Your_application_id > # The key field from App Keys info you copied previously key=< Key > # The Auth Token field from App Keys info you copied previously token=< Token > App code during connection: strAuthMethod = props.getProperty("key"); strAuthToken = props.getProperty("token"); handler = new AppMqttHandler(); handler.connect(serverHost, clientId, strAuthMethod, strAuthToken, isSSL); |
除了 MQTT 提供的身份验证机制之外,IoT 应用程序可能还需要实现额外的安全性来识别合适的设备。本文介绍了一种为这些情形实现基于 OTP 的身份验证的方法。OTP 身份验证是一种保护设备免受不当使用的有用机制,它消除了未授权用户获取访问权的风险。
借助此方法,在设备启动后,只有经过验证的用户才能开始与 IoT 应用程序通信。因为不是所有设备都拥有键盘输入能力,所以可实现一个简单的属性开关来基于设备类型启用或禁用此安全方案。如果启用了 OTP 身份验证,设备会在启动后使用正常的 MQTT 消息向 IoT 代理应用程序发送 OTP 请求。下图展示了详细的流程。
清单 3 展示了如何使用设备属性打开和关闭 OTP 身份验证。如果启用了 OTP 身份验证,设备会在启动后使用正常的 MQTT 消息向 IoT 代理应用程序发送 OTP 请求。
清单 3. OTP 请求
123456789101112 | // Create the request for OTP JSONObject idObj1 = new JSONObject(); try { idObj1.put("event", "server_otp_request"); idObj1.put("deviceId", deviceIdentifier); } catch (JSONException e1) { System.out.println("Exception occurred"); e1.printStackTrace(); } new SendMessageToServer("server_otp_request", idObj1).start(); System.out.println("otp request sent....");} |
IoT 应用程序生成一个 OTP,将它单独发送给设备所有者,并向设备发送一个通知,如下面的清单所示。
清单 4. 生成 OTP
12345678910111213141516171819202122232425 | otp = IOTSecurityUtil.generateOTP();JSONObject jsonObj = new JSONObject();try { jsonObj.put("cmd", "server_otp_response"); jsonObj.put("otp", otp); jsonObj.put("appid", strAppId); jsonObj.put("time", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())); // Server starts a timer of 5 mins during which the // OTP is valid. task = new TimeOutTask(); t = new Timer(); t.schedule(task, 30000000L);} catch (JSONException e) { e.printStackTrace();}System.out.println("Sending otp - " + otp);// Publish command to one specific device// iot-2/type/<type-id>/id/<device-id>/cmd/<cmd-id>/fmt/<format-id>new SendMessageToDevice(strDeviceId, "server_otp_response", jsonObj) .start(); |
如清单 5 所示,OTP 进入了设备,设备将它发送给代理应用程序。代理应用程序验证设备发送的 OTP,并向设备发送一条成功/失败(错误的 OTP 或超时)消息。设备可基于来自配置的重试次数来重新尝试 OTP 身份验证。
如果在重试后 OTP 身份验证仍未成功,应用程序就会关闭。如果未启用 OTP 身份验证,设备将在启动后跳过 OTP 身份验证。
清单 5. 验证 OTP 身份验证
12345678910111213141516171819202122232425262728293031323334 | if (receivedOTP.equals(otp)) { if (task.isTimedOut) { // User took more than 100 seconds and hence the OTP is invalid System.out.println("Time out!"); otpValidated = false; otpTimeOut = true; } else { System.out.println("OTP validated.."); otpValidated = true; otpTimeOut = false } } else { System.out.println("Incorrect OTP.."); otpValidated = false; otpTimeOut = false; } JSONObject otpRespObj = new JSONObject(); try { otpRespObj.put("cmd", "server_otp_validate"); otpRespObj.put("isOTPValid", String.valueOf(otpValidated)); otpRespObj.put("isTimeOut", String.valueOf(otpTimeOut)); otpRespObj.put("appid", strAppId); otpRespObj.put("time", new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss").format(new Date())); } catch (JSONException e) { e.printStackTrace(); } System.out.println("Result of OTP validation - " + otpValidated); // Publish command to one specific device new sendMessageToDevice(strDeviceId, "server_otp_validate", otpRespObj).start();} |
Q物联网 » 设计和构建安全的 IoT 解决方案(1) 保护 IoT 设备和网关