服务接口签名规范

为了降低服务之间接口集成的成本,避免由于接口签名验证方式不同导致的系统互通障碍,因此约定了服务接口签名规范。此接口规范适用于服务与服务之间的接口调用使用。

HTTP接口签名规范

适用于1.0、1.1、2.0版本的HTTP协议。

签名请求参数

发起一次HTTP请求时,需要随请求提供以下请求参数:

名称类型说明备注
accessKeyIdstring标识请求发起方身份的AccessKeyId必须提供,不超过128字节
noncestring请求随机值,用于混淆待签字符串或者标识请求的唯一性必须提供,不少于8字节,不超过128字节
timestampint64请求发起的Unix时间戳,单位为秒

必须提供,服务端需要检查此时间戳和本地时间的差(防止请求一直生效),不超过±120s
 

expiresint64请求过期间隔时间,单位为秒

可选,服务端需要检查 timestamp 参数和本地时间的差是否大于 expires(timestamp的±120s校验无效),适用于预授权、预签名等场景

signedHeadersstring参与签名的HTTP头名称,多个以“,”分隔可选,不超过255字节
principalstring代理的身份,用于代理他人身份访问可选
signaturestring请求签名必须提供,签名计算方法见下节

在HTTP请求中可以通过以下任一方式携带这些请求参数,推荐使用Authorization头携带请求参数,优先使用Authorization头作为请求参数。

通过HTTP Header携带请求参数

使用Authorization头在HTTP Header中携带请求参数。Authorization头是由一个字符串组成的,请求参数需要按以下规则进行构造:

字符串由以下部分组成:

  • 签名类型和签名参数之间使用一个空格分隔
  • 签名参数使用key=value的方式进行连接,多个参数之间使用“; ”分隔(注意:分号后有空格)

通过HTTP QueryString携带请求参数

使用QueryString携带请求参数,请求参数和QueryString参数的对应规则如下:

请求参数名称QueryString参数名称备注
-X-Auth-Algorithm签名类型,例如“LJ-HMAC-SHA256”
accessKeyIdX-Auth-AccessKeyId
nonceX-Auth-Nonce
timestampX-Auth-Timestamp
expiresX-Auth-Expires
signedHeadersX-Auth-SignedHeaders
principalX-Auth-Principal
signatureX-Auth-Signature

一个使用QueryString携带请求参数的请求行示例如下:

GET /hello?X-Auth-Algorithm=LJ-HMAC-SHA256&X-Auth-AccessKeyId=your-ak&X-Auth-Nonce=some-nonce&X-Auth-Timestamp=1519800000&X-Auth-SignedHeaders=X-Request-Id%2CX-Info&X-Auth-Signature=some-signature HTTP/1.1

待签字符串

生成请求签名时,需要将请求参数构造成待签字符串。待签字符串由以下几部分组成:

  • 签名请求参数:accessKeyId、nonce、timestamp、signedHeaders、principal
  • HTTP Method
  • HTTP Path
  • HTTP Query请求参数(需要排除掉X-Auth-Signature参数)
  • HTTP Headers
    • Host头
    • Content-MD5头(参考RFC2616.14.15)若存在需参与签名,用于签名请求体,防止被篡改
    • 在signedHeaders参数中指定的HTTP头

待签字符串的各组成部分的生成规则各有要求,具体如下表所列:

组成部分生成规则示例
签名请求参数以参数名为key,参数值为value
HTTP Method以method为key,大写的HTTP Method为valuemethod=GET
HTTP Path以path为key,不包括查询字符串部分的Path为valuepath=/hello
HTTP Query以query为key,将查询字符串按“&”切分为数组,排除这个字符串数组中以X-Auth-Signature为参数名的项为空的项,按字母正序排序后重新以“&”连接的值为value,无需进行URL解码query=a=b&foo=bar
HTTP Headers

以Header头的小写名称为key(下划线必须替换为中横线),Header值为value

“Host”必须参与签名,理由如下:
1. 避免同一服务多个域名间请求可重放(比如:内网域名生成的签名请求可在外网域名下使用)
2. Http协议规定必须携带Host头信息,符合HTTP协议标准

将各个部分的key-value对以“=”连接,并将所有的key-value对存入一个字符串数组,再把数组以字符序正序排序,最后“&”连接每个key-value对的排序字符串,得到的结果即为待签字符串。生成待签字符串时,若相应部分的参数不存在或者值为空时,此key-value对不出现在待签字符串数组中。

对于下面的请求

GET /hello?foo=bar HTTP/1.0
Host: localhost
X-Request-Id: some-request-id
Authorization: LJ-HMAC-SHA256 accessKeyId=your-ak; nonce=some-nonce; timestamp=1519800000; signedHeaders=X-Request-Id; signature=some-signature

拼装的待签字符串数组为:

[
	"accessKeyId=your-ak",
	"nonce=some-nonce",
	"timestamp=1519800000",
	"signedHeaders=X-Request-Id",
	"method=GET",
	"path=/hello",
	"query=foo=bar",
	"host=localhost",
	"x-request-id=some-request-id"
]

对其按字符正序排序后的结果为:

[ 
	"accessKeyId=your-ak",
	"host=localhost",
	"method=GET",
	"nonce=some-nonce",
	"path=/hello",
	"query=foo=bar",
	"signedHeaders=X-Request-Id",
	"timestamp=1519800000",
	"x-request-id=some-request-id"
]

生成的待签字符串为:

accessKeyId=your-ak&host=localhost&method=GET&nonce=some-nonce&path=/hello&query=foo=bar&signedHeaders=X-Request-Id&timestamp=1519800000&x-request-id=some-request-id

签名算法

签名算法是一种hash函数,将待签字符串和accessKeySecret一起通过hash函数进行计算,结果以Base64编码字符串来表示。必须实现的签名算法是HMAC-SHA256。

以下是几种常见语言的HMAC-SHA256参考实现:

Node版本的签名计算示例:

const crypto = require('crypto');

function sign(stringToSign, accessKeySecret) {
    const hmac = crypto.createHmac('sha256', accessKeySecret);
    hmac.update(stringToSign);

    return hmac.digest('base64');
}

PHP版本的签名计算示例:

function sign($string, $secret)
{
    if (!function_exists('hash_hmac')) {
        throw new \BadMethodCallException('This PHP environment does not support hash library.');
    }

    $hash = hash_hmac('sha256', $string, $secret, true);

    if ($hash === false) {
        throw new \BadMethodCallException('This PHP environment does not support hmac-sha256.');
    }

    return base64_encode($hash);
}

Java版本的签名计算示例,依赖commons-codec、commons-lang:

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.digest.HmacAlgorithms;
import org.apache.commons.codec.digest.HmacUtils;
import org.apache.commons.lang.StringUtils;
 
public static String sign(String stringToSign, String accessKeySecret) {
    byte[] hmac = new HmacUtils(HmacAlgorithms.HMAC_SHA_256, accessKeySecret).hmac(stringToSign);

    return Base64.encodeBase64String(hmac);
}

签名验证结果

服务器端收到请求后应当验证签名的正确性。若当前请求不包含任何可用的身份签名,服务器端应当应答HTTP 401 Unauthorized;若签名不正确,服务器端应当应答HTTP 403 Forbidden。签名验证不通过时,需要在应答Header中通过X-Auth-Result-Code、X-Auth-Result-Message来携带详细的错误信息。应答体中也可携详情信息,但不做具体要求,可根据HTTP的内容协商确定应答体的格式类型。

签名验证不通过时参考的错误码如下:

错误码错误说明
INVALID_PARAMETERS参数不存在或者提供了错误的值
INVALID_PRINCIPAL请求身份不正确
INVALID_SIGNATURE签名不正确
EXPIRED请求时间戳已过期
FORBIDDEN没有访问此资源的权限

SDK

调试

使用Postman发送签名请求

在Postman中,可以使用Pre-request Script来对发送的请求进行加签名,使用下面的脚本,并将签名密钥替换为你自己的密钥:

set environment variable

const accessKeyId = '<your-access-key-id>';
const accessKeySecret = '<your-access-key-secret>';


const nonce = this.request.id;
const timestamp = parseInt(new Date().getTime() / 1000);

const stringToSignArray = [
    `accessKeyId=${accessKeyId}`,
    `nonce=${nonce}`,
    `timestamp=${timestamp}`,
    `method=${this.request.method}`,
    `path=/${pm.request.url.path.join('/')}`
];

if (pm.request.headers.has('Host')) {
    stringToSignArray.push(`host=${pm.request.headers.get("Host")}`);
} else if (pm.environment.has('HOST')) {
    stringToSignArray.push(`host=${pm.environment.get("HOST")}`);
} else {
    stringToSignArray.push(`host=${pm.request.url.host}`);
}

const query = `${pm.request.url.query}`;
var queryArray = query.split("&")
var newQueryArray = []

for (var i=0,len=queryArray.length; i<len; i++)
{ 
    if(queryArray[i]){
        newQueryArray.push(encodeURI(queryArray[i]))
    }
}

var newQueryStr = newQueryArray.sort().join("&")
console.log(newQueryStr)

if (newQueryStr) {
    stringToSignArray.push(`query=${newQueryStr}`);
}

const stringToSign = stringToSignArray.sort().join('&');
const signature = CryptoJS.HmacSHA256(stringToSign, accessKeySecret).toString(CryptoJS.enc.Base64);

console.log(stringToSign);

const authorization = 'LJ-HMAC-SHA256 ' + [
    `accessKeyId=${accessKeyId}`,
    `nonce=${nonce}`,
    `timestamp=${timestamp}`,
    `signature=${signature}`
].join('; ');

console.log(authorization);

pm.globals.set("Authorization", authorization);

设置脚本后,还需要在Headers中指定Authorization头的值为{{Authorization}},以引用签名脚本计算出来的签名值。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值