
# 自定义鉴权
#### 概述
自定义鉴权是指用户可以通过函数工作流（FunctionGraph）服务实现自定义鉴权逻辑，以对接入平台的设备进行身份认证。
在设备接入物联网平台前，用户可以通过应用服务调用控制台配置自定义鉴权信息，然后通过调用函数工作流（[FunctionGraph](https://support.huaweicloud.com/usermanual-functiongraph/functiongraph_01_0110.html)）配置自定义鉴权函数。在设备接入物联网平台时，物联网平台会获取设备ID和自定义鉴权函数名称等参数，并向FunctionGraph发起发起鉴权请求，由用户实现鉴权逻辑以完成设备的接入鉴权。
图1自定义鉴权业务架构图   
![](https://support.huaweicloud.com/devg-iothub/figure/zh-cn_image_0000002339128081.png "点击放大")
#### 使用场景
- 迁移场景：当用户的设备从第三方云平台迁移到IoTDA平台时，存量设备可以依据现有的设备鉴权方式，在平台自定义配置设备的鉴权逻辑，以实现设备鉴权方式免改动迁移。
- 原生场景：用户有自定义实现鉴权逻辑的需求（比如：用户不同的设备有其自定义的认证方式），而无需依赖于平台默认的鉴权方式。
 
#### 约束与限制
- 使用自定义鉴权功能，要求设备必须使用TLS同时支持[SNI(Server Name Indication)](https://datatracker.ietf.org/doc/html/rfc3546#section-3.1)，SNI中需要携带平台分配的域名。
- 每个用户默认最多支持10个自定义鉴权的配置。
- 自定义鉴权的函数最大处理时间为5秒，5秒内函数没返回结果，则认为鉴权失败。
- 每个用户总鉴权请求的TPS限制参考[产品规格说明](https://support.huaweicloud.com/productdesc-iothub/iot_04_0014.html)，自定义鉴权为总鉴权TPS的50%（不包含设备自注册）。
- 若用户开启了缓存FunctionGraph的鉴权结果，则在相同参数下，函数服务的修改生效时间需在缓存超期后才能生效。
- 在所有的设备接入鉴权方式中，当满足自定义鉴权条件时（匹配到设备携带的自定义鉴权器名称或用户配置了默认自定义鉴权器），优先采用自定义鉴权方式进行设备接入。
![](https://support.huaweicloud.com/devg-iothub/public_sys-resources/note_3.0-zh-cn.png)
自定义认证功能是为方便用户快速接入平台，免于设备侧改造。平台将使用您提供的鉴权模板进行鉴权，请合理审视认证方式的安全程度，避免使用弱校验或者免校验。由于您自定义模板安全程度过低造成的安全问题，平台将不承担任何安全责任。
#### 使用说明
图2自定义鉴权操作流程
![](https://support.huaweicloud.com/devg-iothub/figure/zh-cn_image_0000002305248794.png "点击放大")
#### 操作步骤
1. 配置自定义鉴权函数：用户通过调用函数服务（[FunctionGraph](https://support.huaweicloud.com/usermanual-functiongraph/functiongraph_01_0110.html)）创建自定义鉴权函数。单击控制台，搜索函数工作流FunctionGraph服务进入，创建函数。
   
   图3函数列表-创建函数   
   ![](https://support.huaweicloud.com/devg-iothub/figure/zh-cn_image_0000001970149349.png "点击放大")
   图4创建函数-参数信息
   ![](https://support.huaweicloud.com/devg-iothub/figure/zh-cn_image_0000001970160949.png "点击放大")
   
   
2. 创建自定义鉴权：用户可以通过Console配置自定义鉴权信息，IoTDA负责自定义鉴权信息存库和进行相应的管理维护。用户最多支持配置10个自定义鉴权器。
   
   图5自定义鉴权-入口   
   ![](https://support.huaweicloud.com/devg-iothub/figure/zh-cn_image_0000002393411885.png "点击放大")
   图6自定义鉴权-创建鉴权
   ![](https://support.huaweicloud.com/devg-iothub/figure/zh-cn_image_0000001942772362.png "点击放大")
   表1自定义鉴权参数信息 
   | 参数名称       | 是否必选 | 描述                                                                                              |
   |:---|:---|:---|
   | 鉴权名称       | 是    | 自定义鉴权器名称。                                                                                       |
   | 鉴权函数       | 是    | 自定义鉴权器对应的函数名称，从[1]中FunctionGraph已经创建的函数列表中选取。 |
   | 状态         | 是    | 激活停用参数，用于表示该鉴权是否为激活状态，默认为停用状态。若需使用该鉴权器，则应激活后才能生效。                                               |
   | 签名认证       | 是    | 启动签名认证后，不满足签名要求的鉴权信息将被拒绝，以减少无效的函数调用，默认为开启。                                                      |
   | token值     | 否    | 签名校验的token值，开启签名校验时使用，用于认证设备携带的签名信息是否正确。                                                        |
   | 公钥         | 否    | 签名校验的公钥，开启签名校验时使用，用于认证设备携带的签名信息是否正确。                                                            |
   | 是否默认自定义鉴权器 | 是    | 开启后，如果设备发起鉴权时的username没有携带authorizer_name参数，则默认使用此鉴权器。此处默认为不开启。                                 |
   | 是否缓存       | 是    | 缓存开关，用于开启缓存FunctionGraph的鉴权结果，缓存时间为300分钟\~1天，默认不开启。                                             |
      
   
   
3. 设备发起鉴权请求：设备通过MQTT协议发起的CONNECT请求需携带username参数，该参数应包含自定义鉴权的相关可选参数。
   
   - username格式如下。参数传值时需去掉大括号"{}"，username的每个参数需采用分隔符"\|"分隔，各参数具体内容不要出现"\|"，否则可能导致鉴权失败。
     ```
     {device-identifier}|authorizer-name={authorizer-name}|authorizer-signature={token-signature}|signing-token={token-value}
     示例：
     659b70a0bd3f665a471e5ec9_auth|authorizer-name=Test_auth_1|authorizer-signature=***|signing-token=tokenValue
     ```
     表2username参数信息 
     | 参数名称                 | 是否必选 | 描述                                                                                                                       |
     |:---|:---|:---|
     | device-identifier    | 是    | 设备标识符，建议设定为设备id。                                                                                                         |
     | authorizer-name      | 否    | 自定义鉴权器名称，需要同用户配置的鉴权器一致。不携带鉴权器名称时，如用户配置了默认的自定义鉴权器，则使用自定义鉴权，否则使用原有的密钥/证书鉴权方式。                                              |
     | authorizer-signature | 否    | 签名校验功能开启时，需要携带该参数。该参数由私钥和signing-token进行加密后获取。需与[2]中创建自定义鉴权时填写的鉴权名称保持一致。 |
     | signing-token        | 否    | 签名校验功能开启时，需要携带该参数。该参数为签名校验的token，需与[2]中创建自定义鉴权时填写的token值保持一致。            |
        
     
   
   - authorizer-signature通过如下命令行获取：
     ```
     echo -n {signing-token} | openssl dgst -sha256 -sign {private key} | openssl base64
     ```
     表3命令行参数解析 
     | 参数名称                       | 描述                                                                                                                                             |
     |:---|:---|
     | echo -n {signing-token}    | 使用echo命令将签名令牌（signing-token）的值输出，并使用-n参数去掉末尾的换行符。signing-token需与[2]中创建自定义鉴权时填写的**token值**保持一致。 |
     | openssl dgst -sha256 -sign | 使用 SHA256 算法对输入的数据进行哈希处理。                                                                                                                      |
     | {private key}              | 使用 RSA 算法加密的私钥，可传入.pem/.key格式的私钥文件。                                                                                                            |
     | openssl base64             | 将签名结果进行Base64编码，以便于传输和存储。                                                                                                                      |
        
     
   
   
   
   
4. IoTDA处理鉴权请求：IoTDA收到鉴权请求时，会根据username的参数信息和自定义鉴权的配置来决策是否使用自定义鉴权方式。 
   1. 判断username是否携带了自定义鉴权名称，如果有携带，则根据平台的自定义鉴权名称，去匹配鉴权器处理函数。如果没有携带，则使用用户配置的默认自定义鉴权器去匹配鉴权处理函数，若无匹配到则使用原有的密钥/证书鉴权方式。
   
   2. 检查用户是否开启了签名校验，如果开启了签名校验，则校验username携带的签名信息是否可以校验成功，校验失败则直接返回鉴权失败。
   
   3. 匹配到处理函数后，携带设备的鉴权信息（即[5]的入参event）并通过函数的URN（Uniform Resource Name，唯一标识函数。）向FunctionGraph发起鉴权请求。
   
   
   
   
5. 用户实现鉴权逻辑：用户在[1]中通过FunctionGraph创建的处理函数中进行开发，函数的返回结果需要符合要求，函数使用示例及返回的JSON格式要求如下：
   
   ```
   exports.handler = async (event, context) => {
       console.log("username=" + event.username);
       // 此处编写您的校验逻辑
       
       // 返回的JSON格式（格式固定不变）
       const authRes = {
           "result_code": 200,
           "result_desc": "successful",
           "refresh_seconds": 300,
           "device": {
               "device_id": "myDeviceId",
               "provision_enable": true,
               "provisioning_resource": {
                   "device_name": "myDeviceName",
                   "node_id": "myNodeId",
                   "product_id": "myProductId",
                   "app_id": "customization0000000000000000000",
                   "policy_ids": ["657a4e0c2ea0cb2cd831d12a", "657a4e0c2ea0cb2cd831d12b"]
               }
           }
       }
       return JSON.stringify(authRes);
   }
   ```
   其中，函数的请求参数（即event，JSON格式）如下：
   ```
   {
       "username": "myUserName",
       "password": "myPassword",
       "client_id": "myClientId",
       "certificate_info": {
           "common_name": "",
           "fingerprint": "123"
       }
   }
   ```
   表4请求参数信息 
   | 参数名称             | 参数类型       | 是否必选 | 描述                                                                                             |
   |:---|:---|:---|:---|
   | username         | String     | 是    | MQTT协议中CONNECT消息的username字段，与[3]的username格式相同。 |
   | password         | String     | 是    | MQTT协议中CONNECT消息的password参数。                                                                   |
   | client_id        | String     | 是    | MQTT协议中CONNECT消息的clientId参数。                                                                   |
   | certificate_info | JsonObject | 否    | MQTT协议中CONNECT消息的设备证书信息。                                                                       |
      
   表5certificate_info证书信息 
   | 参数名称        | 参数类型   | 是否必选 | 描述                               |
   |:---|:---|:---|:---|
   | common_name | String | 是    | 设备携带设备证书时，从设备证书中解析的commonName信息。 |
   | fingerprint | String | 是    | 设备携带设备证书时，从设备证书中解析的指纹信息。         |
      
   表6返回参数信息 
   | 参数名称            | 参数类型       | 是否必选 | 描述                                                                                                                                   |
   |:---|:---|:---|:---|
   | result_code     | Integer    | 是    | 鉴权结果码， 返回200标识鉴权成功。                                                                                                                  |
   | result_desc     | String     | 否    | 鉴权结果描述信息。                                                                                                                            |
   | refresh_seconds | Integer    | 否    | 鉴权结果的缓存时间，单位(s)。                                                                                                                     |
   | device          | JsonObject | 否    | 认证成功时的设备信息。当设备信息中的设备ID不存在时，如果用户开启自注册[设备自注册](https://support.huaweicloud.com/usermanual-iothub/iot_01_0115.html)，则平台会根据设备信息自动创建对应的设备。 |
      
   表7device设备信息 
   | 参数名称                  | 参数类型       | 是否必选    | 描述                                                                                                                                                            |
   |:---|:---|:---|:---|
   | device_id             | String     | 是       | **参数说明：** 设备ID，自注册场景、非自注册场景都必选。全局唯一，用于唯一标识一个设备。如果携带该参数，平台将设备ID设置为该参数值；建议使用product_id + _ + node_id拼接而成。**取值范围：**长度不超过128，只允许字母、数字、下划线（_）、连接符（-）的组合，建议不少于4个字符。 |
   | provision_enable      | Boolean    | 否       | **参数说明：**自注册开关，默认为false。                                                                                                                                      |
   | provisioning_resource | JsonObject | 自注册场景必选 | **参数说明：**自注册参数信息。                                                                                                                                             |
      
   表8provisioning_resource自注册参数信息 
   | 参数名称        | 参数类型           | 是否必选 | 描述                                                                                                                                                                                                                               |
   |:---|:---|:---|:---|
   | device_name | String         | 否    | **参数说明：** 设备名称，资源空间下唯一，用于资源空间下唯一标识一个设备。 **取值范围：**长度不超过256，只允许中文、字母、数字、以及_?'#().,\&%@!-等字符的组合，建议不少于4个字符。 最小长度：**1** 最大长度：**256** |
   | node_id     | String         | 是    | **参数说明：** 设备标识码，通常使用IMEI、MAC地址或Serial No作为node_id。 设备标识码长度为1到64个字符，包含英文字母、数字、连接号-和下划线_。 注意：NB设备由于模组烧录信息后无法配置，所以NB设备会校验node_id全局唯一。 **取值范围：**长度不超过64，只允许字母、数字、下划线（_）、连接符（-）的组合，建议不少于4个字符。                                         |
   | product_id  | String         | 是    | **参数说明：**设备关联的产品ID，用于唯一标识一个产品模型，创建产品后获得。取值范围：长度不超过256，只允许中文、字母、数字、以及_?'#().,\&%@!-等字符的组合，建议不少于4个字符。 最小长度：**1** 最大长度：**256**     |
   | app_id      | String         | 是    | **参数说明：** 资源空间ID，该参数指定创建的设备归属到哪个资源空间。 **取值范围：**长度不超过36，只允许字母、数字、下划线（_）、连接符（-）的组合。                                                                                                                                                |
   | policy_ids  | List\<String\> | 否    | **参数说明：**Topic策略ID。                                                                                                                                                                                                              |
      
   图7函数编写-部署   
   ![](https://support.huaweicloud.com/devg-iothub/figure/zh-cn_image_0000001970190713.png "点击放大")
   
   
6. FunctionGraph返回结果后，判断是否需要支持自注册功能，如果需要则触发设备的自动注册。自注册设备全部默认为密钥认证，密钥随机生成。IoTDA收到鉴权结果后，进行接下来的业务实现流程。
 
