1. 简介
本系列文章前面三篇介绍了 AWS IoT 支持的所有协议,认证和授权方式的原理和细节,以及其分别所适应的场景。并介绍了设备使用IAM 身份认证,Amazon Cognito 身份和 X.509 身份接入 AWS IoT 的参考步骤和参考代码。接下来本文介绍设备使用自定义身份验证方式接入 AWS IoT 的参考步骤和参考代码。
在进行下一步之前,请确保已执行完本系列文档第一篇的第 4 章,准备工作。
2. 设备使用自定义身份验证接入
您还可以使用自定义的身份验证方法,通过自定义的 Authorizer 来认证设备。自定义的 Authorizer 会触发一个 Lambda 函数来对设备进行认证。 Lambda 函数返回一个 IoT Policy,AWS IoT 根据这个 IoT Policy 来对请求进行鉴权。自定义身份验证方式非常灵活,可以实现自己的特殊需求,例如用在一些有历史遗留问题的系统中,或者设备受到某些特殊的限制而不能使用其他认证方式等场景中。
设备使用自定义身份验证的流程示意图如下:
2.1 部署自定义身份验证的 Authorizer
自定义身份验证是由 Lambda 函数来认证授权,所以先要创建 Lambda 函数。
创建 Lambda 要代入的角色。
IoTDemoAuthorizerFunctionRoleArn=`aws iam create-role \
--role-name IoTDemoAuthorizerFunctionRole \
--assume-role-policy-document "{
\"Version\": \"2012-10-17\",
\"Statement\": [
{
\"Effect\": \"Allow\",
\"Principal\": {
\"Service\": \"lambda.amazonaws.com\"
},
\"Action\": \"sts:AssumeRole\"
}
]
}" | jq .Role.Arn | sed 's/"//g'`
为 Lambda 角色绑定一个 IAM Policy 。
aws iam attach-role-policy --role-name IoTDemoAuthorizerFunctionRole \
--policy-arn arn:aws-cn:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
创建 Lambda 函数。
cat <<EOF > ~/awsIoTAccessDemo/IoTDemoAuthorizerFunction.py
from __future__ import print_function
import json
import base64
def lambda_handler(event, context):
try:
token = json.loads(base64.b64decode(event['token']))
device_id = token['device_id']
policyDocuments = []
policyDocument = {}
policyDocument['Version'] = '2012-10-17'
policyDocument['Statement'] = []
statement0 = {}
statement0['Action'] = []
statement0['Action'].append('iot:Publish')
statement0['Action'].append('iot:Receive')
statement0['Effect'] = 'Allow'
statement0['Resource'] = "arn:aws-cn:iot:cn-north-1:*:topic/IoTDemo/"+device_id
policyDocument['Statement'].append(statement0)
statement1 = {}
statement1['Action'] = 'iot:Subscribe'
statement1['Effect'] = 'Allow'
statement1['Resource'] = "arn:aws-cn:iot:cn-north-1:*:topicfilter/IoTDemo/"+device_id
policyDocument['Statement'].append(statement1)
statement2 = {}
statement2['Action'] = 'iot:Connect'
statement2['Effect'] = 'Allow'
statement2['Resource'] = "arn:aws-cn:iot:cn-north-1:*:client/"+device_id
policyDocument['Statement'].append(statement2)
policyDocuments.append(policyDocument)
authResponse = {}
authResponse['isAuthenticated'] = True
authResponse['principalId'] = device_id.replace('_','')
authResponse['disconnectAfterInSeconds'] = 3600
authResponse['refreshAfterInSeconds'] = 600
authResponse['policyDocuments'] = policyDocuments
print(str(authResponse))
return authResponse
except Exception as e:
print(str(e))
return {}
EOF
zip function.zip IoTDemoAuthorizerFunction.py
IoTDemoAuthorizerFunctionArn=`aws lambda create-function \
--function-name IoTDemoAuthorizerFunction \
--zip-file fileb://function.zip --handler IoTDemoAuthorizerFunction.lambda_handler \
--runtime python2.7 --role ${IoTDemoAuthorizerFunctionRoleArn} \
| jq .FunctionArn | sed 's/"//g'`
创建用于验证 Token 的密钥对。
openssl genrsa -out authorizer_private.pem 2048
openssl rsa -in authorizer_private.pem -outform PEM -pubout -out authorizer_public.pem
创建 Authorizer。
authorizerArn=`aws iot create-authorizer \
--authorizer-name IoTDemoAuthorizer \
--authorizer-function-arn ${IoTDemoAuthorizerFunctionArn} \
--token-key-name IoTDemoAuthorizerToken \
--token-signing-public-keys FIRST_KEY="\`cat authorizer_public.pem\`" \
--status ACTIVE | jq .authorizerArn | sed 's/"//g'`
为 Authorizer 配置调用 Lambda 的权限。
aws lambda add-permission --function-name IoTDemoAuthorizerFunction \
--statement-id IoTDemoAuthorizerFunctionPermission \
--action 'lambda:InvokeFunction' \
--principal iot.amazonaws.com \
--source-arn ${authorizerArn}
2.2 设备使用 HTTP 协议接入
生成设备模拟程序。
cat <<EOF > ~/awsIoTAccessDemo/device_custom_auth_http.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import argparse
import json
import requests
import commands
import base64
#获取参数
parser = argparse.ArgumentParser(description='Send data to IoT Core')
parser.add_argument('--data', default="data from device_custom_auth_http.",
help='data to IoT')
parser.add_argument('--authorizer_name', required=True,
help='custom authorizer name.')
parser.add_argument('--endpoint_prefix', required=True,
help='your iot endpoint prefix.')
parser.add_argument('--private_key', required=True,
help='your custom authorizer private key.')
args = parser.parse_args()
private_data = args.data
endpoint_prefix = args.endpoint_prefix
authorizer_name = args.authorizer_name
private_key = args.private_key
device_name = 'device_custom_auth_http'
region = 'cn-north-1'
private_topic = "IoTDemo/"+device_name
iot_endpoint = "https://%s---iot---cn-north-1.amazonaws.com.rproxy.goskope.com.cn/topics/" % endpoint_prefix
token = {"device_id":device_name}
token_str = base64.b64encode(json.dumps(token))
command = "/bin/echo -n %s | openssl dgst -sha256 -sign %s 2>/dev/null| openssl base64 2>/dev/null" % (token_str,private_key)
return_code, return_str = commands.getstatusoutput(command)
signature = return_str.strip().replace('\n','')
headers = {
"Content-Type": "application/json",
"IoTDemoAuthorizerToken":token_str,
"X-Amz-CustomAuthorizer-Signature":signature,
"X-Amz-CustomAuthorizer-Name":authorizer_name
}
data = json.dumps({"source":device_name, "data":private_data})
endpoint = iot_endpoint + private_topic
r1 = requests.post(endpoint, data=data, headers = headers)
EOF
运行设备模拟程序。
需要注意的是目前实际测试自定义身份验方式下,不支持 ATS endpoint,代码中需注意。另外自定义身份验证也暂时没有 Python SDK 的支持,需要自己编写相关的代码。
python device_custom_auth_http.py \
--data "data from device custom authentication http." \
--authorizer_name IoTDemoAuthorizer \
--endpoint_prefix ${endpoint_prefix} \
--private_key authorizer_private.pem
在本系列文章第一篇的第 4.3 章节打开的控制台中查看 AWS IoT 收到的消息。
2.2 设备使用 MQTT OVER WEBSOCKET 协议接入
生成设备模拟程序。
cat <<EOF > ~/awsIoTAccessDemo/device_custom_auth_websocket.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import json
import commands
import sys
import argparse
import base64
import time
from paho.mqtt.client import Client
import threading
#获取参数
parser = argparse.ArgumentParser(description='Send data to IoT Core')
parser.add_argument('--authorizer_name', required=True,
help='custom authorizer name.')
parser.add_argument('--endpoint_prefix', required=True,
help='your iot endpoint prefix.')
parser.add_argument('--private_key', required=True,
help='your custom authorizer private key.')
args = parser.parse_args()
authorizer_name = args.authorizer_name
endpoint_prefix = args.endpoint_prefix
private_key = args.private_key
device_name = 'device_custom_auth_websocket'
region = 'cn-north-1'
private_topic = "IoTDemo/"+device_name
iot_endpoint = "%s---iot---cn-north-1.amazonaws.com.rproxy.goskope.com.cn" % endpoint_prefix #ATS不支持!!!!!
ca_certs_file = "./VeriSign-Class 3-Public-Primary-Certification-Authority-G5.pem"
# The callback for when the client receives a CONNACK response from the server.
def on_connect(client, userdata, flags, rc):
#print("Connected with result code "+str(rc))
client.subscribe(private_topic)
# The callback for when a PUBLISH message is received from the server.
def on_message(client, userdata, msg):
print("receive message from topic "+msg.topic+", message is "+str(msg.payload))
# This is specific to custom authorizer setup
token = {"device_id":device_name}
token_str = base64.b64encode(json.dumps(token))
command = "/bin/echo -n %s | openssl dgst -sha256 -sign %s 2>/dev/null| openssl base64 2>/dev/null" % (token_str,private_key)
return_code, return_str = commands.getstatusoutput(command)
signature = return_str.strip().replace('\n','')
aws_headers = {
"IoTDemoAuthorizerToken":token_str,
"X-Amz-CustomAuthorizer-Signature":signature,
"X-Amz-CustomAuthorizer-Name":authorizer_name
}
client = Client(device_name, transport="websockets")
client.ws_set_options(headers=aws_headers)
client.tls_set(ca_certs = ca_certs_file)
client.on_connect = on_connect
client.on_message = on_message
client.connect(iot_endpoint, 443, 60)
def pub_msg():
try:
pri_loopCount = 0
while True:
print 'please input:',
msg = raw_input()
private_data = msg
message = {}
message['message'] = json.dumps({"source":device_name, "data":private_data})
message['sequence'] = pri_loopCount
messageJson = json.dumps(message)
client.publish(private_topic, messageJson, 1)
pri_loopCount += 1
time.sleep(2)
except:
sys.exit()
t = threading.Thread(target=client.loop_forever,args=())
t.setDaemon(True)
t.start()
pub_msg()
EOF
运行设备模拟程序。
python device_custom_auth_websocket.py \
--endpoint_prefix ${endpoint_prefix} \
--authorizer_name IoTDemoAuthorizer \
--private_key authorizer_private.pem
此设备模拟程序会一直运行,接受输入的数据,发送到AWS IoT Core,同时也订阅自己发送消息的topic。
输入要发送到 AWS IoT 的消息,如 “data from device custom authorization websocket.”,设备会接收到自己发送的这个消息。同时,在本系列文章第一篇的 4.3 章节中打开的控制台中也可以看到此消息。
执行 Ctrl+C 停止程序。
3. 资源清理(可选)
aws iam detach-role-policy --role-name IoTDemoAuthorizerFunctionRole --policy-arn arn:aws-cn:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
aws iam delete-role --role-name IoTDemoAuthorizerFunctionRole
aws lambda delete-function --function-name IoTDemoAuthorizerFunction
aws iot update-authorizer --authorizer-name IoTDemoAuthorizer --status INACTIVE
aws iot delete-authorizer --authorizer-name IoTDemoAuthorizer
rm -f ~/awsIoTAccessDemo/*
4. 总结
本系列文章介绍了 AWS IoT 支持的所有协议,认证和授权方式,及其各种接入场景下具体的实现细节。这可以帮助我们选择更合适的方式连接到 AWS IoT,也可以让我们更快的把设备接入到 AWS IoT 中。
本篇作者