OneNET消息佇列MQ服務鑒權機制主要通過如下方式保證訪問安全:
避免核心金鑰網路中直接傳輸,從而避免核心金鑰在傳輸中洩露
通過包含由非可逆算法生成的簽名的token來進行身份認證,即使token被竊取,攻擊者也無法通過token反向獲得核心金鑰
鑒權參數token具有用戶自訂的過期時間屬性,可從時間維度降低被攻擊/仿冒的風險
token由多個參數構成,如下表:
Item | 類型 | 是否必須 | 參數說明 | 參數示例 |
---|---|---|---|---|
version | string | 是 | 參數組版本號,日期格式,目前僅支持'2018-10-31' | 2018-10-31 |
res | string | 是 | 固定為mqs/{實例名稱} | mqs/test_mq |
et | int | 是 | 訪問過期時間expirationTime,unix時間當一次訪問參數中的et時間小於當前時間時,平台會認為訪問參數過期從而拒絕該訪問 | 1537255523 表示:北京時間2018-09-18 15:25:23 |
method | string | 是 | 簽名方法signatureMethod 支持md5、sha1、sha256 | sha256 |
sign | string | 是 | 簽名結果字串signature |
關於token參數的特別說明:
參數sign的生成演算法為:
sign = base64(hmac_<method>(base64decode(accessKey), utf-8(StringForSignature)))
其中:
accessKey為OneNET為獨立資源配置的唯一訪問金鑰,其作為簽名演算法參數之一參與簽名計算,為保證訪問安全,請妥善保管
accessKey參與計算前應先進行base64decode操作
用於計算簽名的字串StringForSignature的組成順序按照參數名稱進行字串排序,以'/n'作為參數分隔,當前版本中按照如下順序進行排序:et、method、res、version
StringForSignature組成示例如下:
StringForSignature = et + '\n' + method + '\n' + res+ '\n' + version
注意:每個參數均為key=value格式組成,但是僅參數中的value參與計算簽名的字串StringForSignature的組成,若token參數如下
et = 1537255523
method = sha1
res = mqs/test_mq
version = 2018-10-31
則用於計算簽名的字串StringForSignature為(按照et、method、res、version的順序)
StringForSignature = "1537255523" + "\n" + "sha1"+ "\n" + "mqs/test_mq"+ "\n" + "2018-10-31"
計算出sign後,將每個參數均採用key=value的形式表示,並用'&'作為分隔符號,示例如下:
version=2018-10-31&res=mqs/test_mq&et=1537255523&method=sha1&sign=ZjA1NzZlMmMxYzIOTg3MjBzNjYTI2MjA4Yw=
token中key=value的形式的value部分需要經過URL編碼,需要進行編碼的特殊符號如下:
序號 | 符號 | 編碼 |
---|---|---|
1 | + | %2B |
2 | 空格 | %20 |
3 | / | %2F |
4 | ? | %3F |
5 | % | %25 |
6 | # | %23 |
7 | & | %26 |
8 | = | %3D |
編碼後,上例中實際傳輸token為:
version=2018-10-31&res=mqs%2Ftest_mq&et=1537255523&method=sha1&sign=ZjA1NzZlMmMxYzIOTg3MjBzNjYTI2MjA4Yw%3D
python代碼示例
import base64
import hmac
import time
from urllib.parse import quote
def token(name,access_key):
version = '2018-10-31'
res = 'mqs/%s' % name # 通過MQ實例名稱訪問MQ
# 用戶自訂token過期時間
et = str(int(time.time()) + 3600)
# 簽名方法,支持md5、sha1、sha256
method = 'sha1'
# 對access_key進行decode
key = base64.b64decode(access_key)
# 計算sign
org = et + '\n' + method + '\n' + res + '\n' + version
sign_b = hmac.new(key=key, msg=org.encode(), digestmod=method)
sign = base64.b64encode(sign_b.digest()).decode()
# value 部分進行url編碼,method/res/version值較為簡單無需編碼
sign = quote(sign, safe='')
res = quote(res, safe='')
# token參數拼接
token = 'version=%s&res=%s&et=%s&method=%s&sign=%s' % (version, res, et, method, sign)
return token
if __name__ == '__main__':
name = 'test_mq'
access_key = 'KuF3NT/jUBJ62LNBB/A8XZA9CqS3Cu79B/ABmfA1UCw='
print(token(name,access_key))
Java代碼示例
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
public class Token {
public static String assembleToken(String version, String resourceName, String expirationTime, String signatureMethod, String accessKey)
throws UnsupportedEncodingException, NoSuchAlgorithmException, InvalidKeyException {
StringBuilder sb = new StringBuilder();
String res = URLEncoder.encode(resourceName, "UTF-8");
String sig = URLEncoder.encode(generatorSignature(version, resourceName, expirationTime
, accessKey, signatureMethod), "UTF-8");
sb.append("version=")
.append(version)
.append("&res=")
.append(res)
.append("&et=")
.append(expirationTime)
.append("&method=")
.append(signatureMethod)
.append("&sign=")
.append(sig);
return sb.toString();
}
public static String generatorSignature(String version, String resourceName, String expirationTime, String accessKey, String signatureMethod)
throws NoSuchAlgorithmException, InvalidKeyException {
String encryptText = expirationTime + "\n" + signatureMethod + "\n" + resourceName + "\n" + version;
String signature;
byte[] bytes = HmacEncrypt(encryptText, accessKey, signatureMethod);
signature = Base64.getEncoder().encodeToString(bytes);
return signature;
}
public static byte[] HmacEncrypt(String data, String key, String signatureMethod)
throws NoSuchAlgorithmException, InvalidKeyException {
//根據給定的位元組陣列構造一個金鑰,第二參數指定一個金鑰演算法的名稱
SecretKeySpec signinKey = null;
signinKey = new SecretKeySpec(Base64.getDecoder().decode(key),
"Hmac" + signatureMethod.toUpperCase());
//生成一個指定Mac 演算法的Mac 物件
Mac mac = null;
mac = Mac.getInstance("Hmac" + signatureMethod.toUpperCase());
//用給定金鑰初始化Mac 物件
mac.init(signinKey);
//完成 Mac 操作
return mac.doFinal(data.getBytes());
}
public enum SignatureMethod {
SHA1, MD5, SHA256;
}
public static void main(String[] args) throws UnsupportedEncodingException, NoSuchAlgorithmException, InvalidKeyException {
String version = "2018-10-31";
String resourceName = "mqs/test_mq";
String expirationTime = System.currentTimeMillis() / 1000 + 100 * 24 * 60 * 60 + "";
String signatureMethod = SignatureMethod.SHA1.name().toLowerCase();
String accessKey = "KuF3NT/jUBJ62LNBB/A8XZA9CqS3Cu79B/ABmfA1UCw=";
String token = assembleToken(version, resourceName, expirationTime, signatureMethod, accessKey);
System.out.println("Authorization:" + token);
}
}