平台签名校验
本文为您介绍了如何生成请求签名。
对于每一次HTTPS(HTTP、Websocket)协议 的请求,我们会根据访问中的签名信息验证访问请求者身份。具体由使用 APP Key和 APP Secret对称加密验证实现。其中 APP Key是 获取访问授权的身份,APP Secret是加密签名字符串和服务器端验证签名字符串的密钥,必须严格保密谨防泄露。
请求规范
- 所有URL参数必须按照RFC3986规范进行URLEncode,其中空格同时支持
%20
和+
,不同开发语言的用户基本可使用普遍的URLEncode实现 - 包含Body的请求,请求头必须指定正确的
Content-Type
和Content-Length
- 为防止重放攻击,请求包含一个随机串(见下一节),用户必须保证同一个应用(APP Key)在 4小时内的唯一性,否则请求将被拒绝(HTTP 403)
步骤一:构造请求头
通过 HTTP头提交公共签名参数,参数见下表:
HTTP Header | 说明 |
---|---|
X-OPA-APP-KEY | APP Key,在PaaS平台创建应用获得 |
X-OPA-TIMESTAMP | 客户端当前时间戳,与服务器时差必须在24H以内 |
X-OPA-NONCE | 随机串,建议使用UUID 或其他可保证唯一的方式处理 |
X-OPA-SIGN-METHOD | 签名方法,目前支持 hmac-sha1(缺省), hmac-sha256, hmac-sha521 |
步骤二:构造待签名字符串
请求签名为请求信息通过签名算法(HTTP头指定)以及APP Secret
运算得到。下面步骤为构造请求签名原文的方法:
假设相关数据如下:
# HTTPMethod 请求方法
GET
# Path 完整路径
/sl/v1/smart-plug
# URL参数
sn :xx,
action :1,
index :1,
_format :json
# Nonce 随机串
d0d623d70e2caf73c53f40f1f998011a
# APP Secret
bbb
把URL参数的键按本地字序升序排列,构造出如下字符串SortedQueryString:
_format=json&action=1&index=1&sn=xx
然后按照以下结构构造待签名字符串(去除拼接符+)
UPPER(HTTPMethod) + Path + SortedQueryString + Nonce
结果为:
GET/sl/v1/smart-plug/get-status_format=json&action=1&index=1&sn=xxd0d623d70e2caf73c53f40f1f998011a
步骤三:签名并附加参数
把步骤二的结果与APP Secret
通过指定的签名方法获得请求的签名。
base64_encode(hash_hmac('sha1', 'GET/sl/v1/smart-plug/get-status_format=json&action=1&index=1&sn=xxd0d623d70e2caf73c53f40f1f998011a', 'bbb', true));
得到签名:R/79bgitE7UtVTs2albooqfG2YI=
URLEncode:R%2F79bgitE7UtVTs2albooqfG2YI%3D
把上面结果通过参数名_signature
附加到URL参数中,得到完整的请求如下(注意对签名结果的URLEncode):
GET /sl/v1/smart-plug/get-status?sn=xx&action=1&index=1&_format=json&_signature=R%2F79bgitE7UtVTs2albooqfG2YI%3D HTTP/1.1
Host: api.oraydev.cn
Accept: */*
X-OPA-APP-KEY: aaa
X-OPA-TIMESTAMP: 1724317445
X-OPA-NONCE: d0d623d70e2caf73c53f40f1f998011a
X-OPA-SIGN-METHOD: hmac-sha1
示例代码
以下提供完整的请求示例PHP
代码以作参考
<?php
// 替换成自己的
$appKey = '';
$appSecret = '';
// 随机串,需自行实现生成规则
$nonce = generateNonce();
// header参数
$headers = [
'X-OPA-APP-KEY' => $appKey,
'X-OPA-TIMESTAMP' => time(),
'X-OPA-NONCE' => $nonce,
'X-OPA-SIGN-METHOD' => 'hmac-sha1'
];
// ############## POST请求示例 #################
$method = 'POST';
$path = '/common/v1/authorization';
$params = [];
// 生成签名,注意$params只是url需要携带的参数
$params["_signature"] = generateSign($appSecret, $method, $path, $nonce, $params, 'sha1');
// 包含签名参数的URL
$url = "https://api.oraydev.cn{$path}?" . http_build_query($params);
$data = [
'account' => '',
'password' => ''
];
// 发送请求
$resp = sendHttpRequest($url, $method, $headers, $data, 'json');
// 错误处理
if (!isset($resp['status_code']) || !($resp['status_code'] >= 200 && $resp['status_code'] < 300)) {
// ...
return;
}
// ############## GET请求示例 #################
$method = 'GET';
$path = '/sl/v1/smart-plug/get-status';
$params = [
'sn' => 'xx',
'action' => 1,
'index' => 1,
'_format' => 'json'
];
// 生成签名,注意$params只是url需要携带的参数
$params['_signature'] = generateSign($appSecret, $method, $path, $nonce, $params, 'sha1');
// 包含签名参数的URL
$url = "https://api.oraydev.cn{$path}?" . http_build_query($params);
// 发送请求
$resp = sendHttpRequest($url, $method, $headers);
// 错误处理
if (!isset($resp['status_code']) || !($resp['status_code'] >= 200 && $resp['status_code'] < 300)) {
// ...
return;
}
/**
* 生成签名
*
* @param $appSecret
* @param $method
* @param $path
* @param $nonce
* @param $params
* @param $algo sha1|sha256|sha521
* @return string
*/
function generateSign($appSecret, $method, $path, $nonce, $params = [], $algo = 'sha1')
{
// 参数按键升序排列
ksort($params);
$qs = [];
foreach ($params as $k => $v) {
$qs[] = $k . '=' . $v;
}
// 用&切割成字符串
$sortedQueryString = implode('&', $qs);
// 必须全部大写
$method = strtoupper($method);
// 组装签名字符串
$signStr = $method . $path . $sortedQueryString . $nonce;
// 进行hash签名后进行base64编码
$sign = base64_encode(hash_hmac($algo, $signStr, $appSecret, true));
return $sign;
}
/**
* 发送http请求
*
* @param $url
* @param $method GET|POST
* @param $headers
* @param $data
* @param $dataType json|form-data
* @return array
*/
function sendHttpRequest($url, $method = 'GET', $headers = [], $data = [], $dataType = 'json')
{
$ch = curl_init();
// 设置通用选项
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
switch (strtoupper($method)) {
case 'POST':
curl_setopt($ch, CURLOPT_POST, true);
if ($dataType === 'json') {
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
$headers['Content-Type'] = 'application/json';
} elseif ($dataType === 'form-data') {
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data));
$headers['Content-Type'] = 'application/x-www-form-urlencoded';
} else {
throw new InvalidArgumentException('Unsupported data type: ' . $dataType);
}
break;
case 'GET':
if (!empty($data)) {
$url .= '?' . http_build_query($data);
curl_setopt($ch, CURLOPT_URL, $url);
}
break;
default:
throw new InvalidArgumentException('Unsupported request method: ' . $method);
}
// 格式化header
$newHeaders = [];
foreach ($headers as $k => $v) {
$newHeaders[] = $k . ':' . $v;
}
curl_setopt($ch, CURLOPT_HTTPHEADER, $newHeaders);
$response = curl_exec($ch);
$info = curl_getinfo($ch);
if (curl_errno($ch)) {
$error = curl_error($ch);
curl_close($ch);
throw new RuntimeException('CURL error: ' . $error);
}
curl_close($ch);
$data = [
'body' => $response,
'status_code' => $info['http_code'],
];
return $data;
}
以下提供完整的请求示例Python
代码以作参考
import base64
import hashlib
import hmac
import json
import time
import urllib
import urllib.parse
import uuid
import requests
class OrayDevApi(object):
def __init__(self, api_key, api_secret):
self.__api_key = api_key
self.__api_secret = api_secret
self.__api_host = 'https://api.oraydev.cn'
def send_request(self, method, path, query, headers, data):
"""
发起请求(自动签名)
:param method: 请求方法
:param path: 接口路径
:param query: query参数
:param headers: header参数
:param data: body参数
"""
# 生成一个随机的 UUID 作为 nonce
nonce = str(uuid.uuid4())
timestamp = int(time.time())
# 计算请求签名,并传入query
signature = self.__generate_signature__(method, path, query, nonce)
query['_signature'] = signature
# 签名公共请求头
sign_headers = {
'X-OPA-APP-KEY': self.__api_key,
'X-OPA-TIMESTAMP': str(timestamp),
'X-OPA-NONCE': nonce,
'X-OPA-SIGN-METHOD': 'hmac-sha256'
}
if headers is not None:
sign_headers.update(headers)
# 发送请求
query_string = urllib.parse.urlencode(query)
url = f"{self.__api_host}{path}?{query_string}"
return requests.request(method, url, headers=sign_headers, data=data)
def __generate_signature__(self, method, path, query, nonce):
"""
计算请求签名
https://paas.oray.com/documentation/a-21.html?uplocation=1
:param method: 请求方法
:param path: 接口路径
:param query: query参数
:param nonce: 随机值
:return:
"""
# 参数按键升序排列
sorted_params = sorted(query.items())
# 用 & 切割成字符串
sorted_query_string = "&".join(f"{k}={str(v)}" for k, v in sorted_params)
# 构造待签名字符串
sign_str = f"{method.upper()}{path}{sorted_query_string}{nonce}"
# 计算签名
hmac_obj = hmac.new(self.__api_secret.encode('utf-8'), sign_str.encode('utf-8'), hashlib.sha256)
return base64.b64encode(hmac_obj.digest()).decode('utf-8')
if __name__ == '__main__':
client = OrayDevApi(
api_key='<yourApiKey>',
api_secret='<yourApiSecret>'
)
# POST请求示例: 登录账号
# https://paas.oray.com/documentation/a-7.html?uplocation=1
resp = client.send_request(
method='POST',
path='/common/v1/authorization',
query={},
headers={
'Accept': 'application/json',
'Content-Type': 'application/json',
},
data=json.dumps({
'account': '<yourAccount>',
'password': '<yourPassword>'
})
)
print(resp, resp.text)
# GET请求示例: 获取插座状态
# https://paas.oray.com/documentation/a-23.html?uplocation=1
resp = client.send_request(
method='GET',
path='/sl/v1/smart-plug/get-status',
query={
'sn': '<设备SN>',
'_format': 'json'
},
headers={
'Accept': 'application/json',
'Content-Type': 'application/json',
},
data=None
)
print(resp, resp.text)
最后修改时间: 6 天前
**请求规范**
**步骤一:构造请求头**
**步骤二:构造待签名字符串**
**步骤三:签名并附加参数**
**示例代码**