Skip to content

签名算法说明

接口鉴权需要wps4签名的接口,header中需要携带以下字段:

参数参数类型是否必须说明
Content-Typestring固定为:“application/json”
Wps-Docs-Datestring取当前时间,格式:“Wed, 23 Jan 2013 06:43:08 GMT”
Wps-Docs-Authorizationstring签名值,格式:“WPS-4 accesskey“:Signature”

Wps-Docs-Authorization计算方法

接口签名时,只签uri字段,不签域名,具体计算规则如下:

Wps-Docs-Authorization:"WPS-4 $access_key:$Signature" 
Signature:hmac-sha256(secret_key, Ver + HttpMethod + URI + Content-Type + Wps-Docs-Date + sha256(HttpBody))

Ver + HttpMethod + URI + Content-Type + 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”。
  • Date:自行对签名时限进行验证。
  • HttpBody:如果为空,则sha256(body)部分取空串。

Go代码示例

import (
    "crypto/hmac"
    "crypto/sha256"
    "encoding/hex"
    "fmt"
    "net/http"
    "net/url"
    "openapi/common/encryption"
    "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(Wps4AuthSign, auth)
    header.Set(contentTypeSign, contentType)
    header.Set(wps4DateSign, date)
    header.Set(connectionSign, "keep-alive")
    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 {
        bodySha = encryption.GetSha256(content)
    }

    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))
}

测试方法

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代码示例

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.text

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工具类代码:

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();
    }
}

Postman脚本示例

var accessKey=pm.environment.get("accessKey")
var secretKey=pm.environment.get("secretKey")
// console.log(`accessKey=${accessKey}, secretKey=${secretKey}, appToken=${appToken}`)

// 获取请求信息
var method = pm.request.method
var path=pm.environment.replaceIn(pm.request.url.getPathWithQuery())
var body = pm.environment.replaceIn(pm.request.body.toString())
var contentType="application/json"
var dateString = new Date().toGMTString();
var authorization = "";
if(body==""){
    console.log(`[${method}] ${path}`)
    authorization = 'WPS-4' + method + path + contentType + dateString 
} else {
    console.log(`[${method}] ${path}\n${body}`)
    authorization = 'WPS-4' + method + path + contentType + dateString +  CryptoJS.SHA256(body).toString(CryptoJS.enc.Hex)
}

console.log(`authorization ${authorization}`)
authorization = CryptoJS.HmacSHA256(authorization, secretKey).toString(CryptoJS.enc.Hex)
authorization = 'WPS-4 ' +accessKey + ':' + authorization

// 设置请求头
pm.request.headers.upsert({
    key: 'Wps-Docs-Date',
    value: dateString
})
pm.request.headers.upsert({
    key: 'Content-Type',
    value: contentType
})
pm.request.headers.upsert({
    key: 'Wps-Docs-Authorization',
    value: authorization
})
// console.log(pm.request.getHeaders());