WPS-4签名主要用于外部三方调用时接口签名。
签名算法说明
接口签名时,只签uri字段(不包含/o/{cid}部分),不签域名,如:/o/docmini/api/xxx,只签/api/xxx部分 Header中需要携带以下字段:
| 参数 | 参数类型 | 是否必须 | 说明 |
|---|---|---|---|
| Content-Type | string | 否 | 默认为"application/json",跟实际请求的ContentType保持一致 |
| Wps-Docs-Date | string | 是 | 取当前时间,格式:"Wed, 23 Jan 2013 06:43:08 GMT" |
| Wps-Docs-Authorization | string | 是 | 签名值 |
特别说明:当Content-Type中不包含application/json或application/x-www-form-urlencoded类型时,Body不计入签名计算,此外参与计算的Content-Type需要跟实际请求的ContentType保持一致
- 示例①:请求Header 为
Content-Type: multipart/form-data; boundary=fdb1369bd955e779bdf793ff7e2f3764,则计算签名时,Content-Type="multipart/form-data; boundary=fdb1369bd955e779bdf793ff7e2f3764", Body="" - 示例②:请求Header 为
Content-Type: application/json或者Content-Type: application/json; charset=utf-8,则计算签名时,Content-Type="application/json"或者"application/json; charset=utf-8", Body计入签名计算
计算方法如下:
Wps-Docs-Authorization:"WPS-4 $AppId:$Signature"
Signature:hmac-sha256(AppKey, Ver + HttpMethod + URI + Content-Type + Wps-Docs-Date + sha256(HttpBody))Ver + HttpMethod + URI + Content-Type + Wps-Docs-Date + sha256(HttpBody) 示例: WPS-4POST/callback/path/demoapplication/jsonWed, 20 Apr 2022 01:33:07 GMTfc005f51a6e75586d2d5d078b657dxxxdf9c1dfa6a7c0c0ba38c715daeb6ede9
sha256 与 hmac-sha256 均取小写十六进制字符串。
- Ver: WPS-4,表示算法版本,后续算法有更新,则变更该字段。
- HttpMethod:表示HTTP 请求的Method的字符串,如PUT、GET、POST、HEAD、DELETE等。
- URI:不带域名,如:"/api_url?app_id=aaaa"。
- Content-Type:表示请求内容的类型,如:"application/json"。
- Wps-Docs-Date:自行对签名时限进行验证。
- HttpBody:如果为空,则sha256(body)部分取空串。
Go代码示例
go
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"fmt"
"net/http"
"net/url"
"strings"
"time"
)
type Wps4Auth struct {
AccessKey string
SecretKey string
}
func NewWps4Auth(accessKey, secretKey string) *Wps4Auth {
auth := &Wps4Auth{}
auth.AccessKey = accessKey
auth.SecretKey = secretKey
return auth
}
func (a *Wps4Auth)BuildWps4Headers(method string, url *url.URL, data []byte, contentType string) *http.Header {
if strings.TrimSpace(contentType) == "" {
contentType = "application/json"
}
header := http.Header{}
auth, date := a.prepare(method, url, data, contentType)
header.Set("Wps-Docs-Authorization", auth)
header.Set("Content-Type", contentType)
header.Set("Wps-Docs-Date", date)
return &header
}
func (a *Wps4Auth) prepare(method string, url *url.URL, data []byte, contentType string) (auth, date string) {
path := url.Path
if url.RawQuery != "" {
path += fmt.Sprintf("?%s", url.RawQuery)
}
var content []byte
if data != nil {
content = data
}
date = time.Now().UTC().Format(http.TimeFormat)
sig := a.sign(method, path, contentType, date, content)
auth = fmt.Sprintf("WPS-4 %s:%s", a.AccessKey, sig)
return
}
func (a *Wps4Auth) sign(method, uri, contentType, date string, content []byte) (sign string) {
bodySha := ""
if content != nil {
s := sha256.New()
s.Write(content)
bodySha = hex.EncodeToString(s.Sum(nil))
}
mac := hmac.New(sha256.New, []byte(a.SecretKey))
mac.Write([]byte("WPS-4"))
mac.Write([]byte(method))
mac.Write([]byte(uri))
mac.Write([]byte(contentType))
mac.Write([]byte(date))
mac.Write([]byte(bodySha))
return hex.EncodeToString(mac.Sum(nil))
}测试方法
go
import (
"encoding/json"
"net/url"
"testing"
)
type TestWps4Body struct {
MsgType string `json:"msg_type" web:"msg_type,required"`
MsgData string `json:"msg_data,omitempty" web:"msg_data"`
}
func TestWps4Sign(t *testing.T) {
accessKey := "****************"
secretKey := "********************************"
wps4Auth := NewWps4Auth(accessKey, secretKey)
method := "GET"
path := "/kopen/pay/v1/wx_adapter/mp/subscribe/wps_docer?access_token=********************************'"
header := wps4Auth.BuildWps4Headers(method, &url.URL{Path: path}, nil, "application/json")
t.Log(header)
method = "POST"
path = "/kopen/pay/v1/msg/apipush?access_token=********************************'"
body := &TestWps4Body{MsgType: "wps_docer_attent_reward", MsgData: "{'account': 'Zyssnh事实上&abc=123kk//dsa?d'}"}
reqData, err := json.Marshal(body)
if err != nil {
t.Error(err)
}
header = wps4Auth.BuildWps4Headers(method, &url.URL{Path: path}, reqData, "application/json")
t.Log(header)
}Python代码示例
python
import json
import requests
import hashlib
import time
# 应用信息
def _wps4_sig(method, url, date, body):
if body is None:
bodySha = ""
else:
bodySha = hashlib.sha256(body.encode('utf-8')).hexdigest()
content = "WPS-4" + method + url + "application/json" + date + bodySha
signature = hmac.new(secret_key.encode('utf-8'), content.encode('utf-8'), hashlib.sha256).hexdigest()
return "WPS-4 %s:%s" % (access_key, signature)
def wps4_request(method, host, uri, body=None, cookie=None, headers=None):
requests.packages.urllib3.disable_warnings()
if body is not None:
body = json.dumps(body)
date = time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime())
# date = "Wed, 02 Jun 2021 12:15:40 GMT"
# print date
header = {"Content-type": "application/json"}
header['Wps-Docs-Date'] = date
header['Wps-Docs-Authorization'] = _wps4_sig(method, uri, date, body)
if headers != None:
# header = {}
for key, value in headers.items():
header[key] = value
url = "%s%s" % (host, uri)
r = requests.request(method, url, data=body,
headers=header, cookies=cookie, verify=False)
return r.status_code, r.textjava代码示例
java
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution)
throws IOException {
//获取uri路径
String path = request.getURI().getPath() ;
if(request.getURI().getRawQuery() != null && !"".equals(request.getURI().getRawQuery())) {
path = path + "?" + request.getURI().getRawQuery();
}
//获取Content-type,“application/json"
String contentType = request.getHeaders().getContentType().toString();
//日期格式化
DateFormat dateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss 'GMT'", Locale.US);
dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
String date = dateFormat.format(new Date());
//open不参与签名,做替换处理
if (path.startsWith("/open")) {
path = path.replace("/open", "");
}
String sha256body;
//body为空则为空,否则返回sha256(body)
if (body != null && body.length > 0) {
sha256body = HMacUtils.getSHA256StrJava(body);
} else {
sha256body = "";
}
//hmac-sha256(secret_key, Ver + HttpMethod + URI + Content-Type + Wps-Date + sha256(HttpBody))
String signature = null;
try {
signature = HMacUtils.HMACSHA256("WPS-4" + request.getMethod() + path + contentType + date + sha256body,secretKey);
} catch (Exception e) {
e.printStackTrace();
}
request.getHeaders().add("Wps-Docs-Date",date);
request.getHeaders().add("Wps-Docs-Authorization", String.format("WPS-4 %s:%s", accessKey, signature));
return execution.execute(request, body);
}sha256和hmac工具类代码:
java
package demo.utils;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class HMacUtils {
public static String HMACSHA256(String data, String key) throws Exception {
Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
SecretKeySpec secret_key = new SecretKeySpec(key.getBytes("UTF-8"), "HmacSHA256");
sha256_HMAC.init(secret_key);
byte[] array = sha256_HMAC.doFinal(data.getBytes("UTF-8"));
StringBuilder sb = new StringBuilder();
for (byte item : array) {
sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
}
return sb.toString();
}
/**
* 利用java原生的摘要实现SHA256加密
* @param str 加密后的报文
* @return
*/
public static String getSHA256StrJava(byte[] str){
MessageDigest messageDigest;
String encodeStr = "";
try {
messageDigest = MessageDigest.getInstance("SHA-256");
messageDigest.update(str);
encodeStr = byte2Hex(messageDigest.digest());
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return encodeStr;
}
/**
* 将byte转为16进制
* @param bytes
* @return
*/
private static String byte2Hex(byte[] bytes){
StringBuffer stringBuffer = new StringBuffer();
String temp = null;
for (int i=0;i<bytes.length;i++){
temp = Integer.toHexString(bytes[i] & 0xFF);
if (temp.length()==1){
//1得到一位的进行补0操作
stringBuffer.append("0");
}
stringBuffer.append(temp);
}
return stringBuffer.toString();
}
}验证用例
拿下面请求参数签名
- appId:
**************** - appKey:
******************************** - url:
/manage/v1/application/config/info - method:
GET - Content-Type:
application/json - Wps-Docs-Date:
Sat, 07 Oct 2023 02:53:09 GMT - body:空
签名结果如下:
Content-Type:application/jsonWps-Docs-Date:Sat, 07 Oct 2023 02:53:09 GMTWps-Docs-Authorization:WPS-4 ****************:********************************
常见签名报错问题
根据返回结果中的debug参数信息,对比下本地签名的参数是否与之匹配,常见出错点:
- SK不一致(SK不能写死,因为不同环境SK 不同,需要从环境变量中获取)
- 实际签名的URI或Body不一致
- 签名使用的ContentType与实际请求的不一致