接口签名鉴权
该鉴权方式支持合作平台使用开放平台接口
接口域名
https://open.wesurvey.com
请求结构
- API 的所有接口均通过
HTTPS
进行通信,均使用UTF-8
编码 - 支持的 HTTP 请求方法:POST、GET、PUT、DELETE
- POST 请求支持的 Content-Type 类型:
application/json
,后续文档中参数位置为 body 则放在 json 传入 - 注意:请勿在前端直接发起开放接口请求,防止泄露 secret
公共参数
1. 参数说明
用于标识用户和接口鉴权目的的参数,如非必要,在每个接口单独的接口文档中不再对这些参数进行说明,但每次请求均需要携带这些参数,才能正常发起请求。 公共参数定义如下:
参数名 | 描述 | 参考值 |
---|---|---|
appid | 第三方应用id | tpidGFSJgefA |
timestamp | 当前时间戳 | 1615783862 |
nonce | 随机正整数 | 815390 |
sign | 签名 | ff47fd770c11936a14435c2a8f15fa6626c90464 |
2.GET 请求结构示例
(该接口用于签名方法调试,无实际用途)
https://open.wesurvey.com/api/signature/check?appid=tpidGFSJgefA&nonce=26377876×tamp=1615794722&sign=70e45a2659340a9d97e1115483e265c0a1e3a207
3.POST 请求结构示例
(该接口用于签名方法调试,无实际用途)
https://open.wesurvey.com/api/signature/check?appid=tpidGFSJgefA&nonce=83990929×tamp=1615795350&sign=e9c028ec5e3fcbdf7c7202d2fc5017e78ae7cf33
{"input":"ping"}
接口鉴权
1.申请应用凭证
- 使用开放接口前须先后台申请 appid 和 secret
- API 会对每个访问请求进行身份验证,即每个请求都需要在公共请求参数中包含签名信息(Signature)以验证请求者身份
- 签名信息由安全凭证生成,安全凭证为 secret, 用于加密签名字符串和服务器端验证签名字符串的密钥(即 sign 参数)
2.对请求参数排序
- 首先对所有URL中的请求参数(不包括sign,不包括data)按参数名的字典序( ASCII 码)升序排序
- 用户可以借助编程语言中的相关排序函数来实现这一功能,如 PHP 中的 ksort 函数、Golang 中的 sort.Strings(keys)
- 上述GET示例参数的排序结果如下:
appid、nonce、timestamp
3.拼接 query 字符串
- 此步骤生成URL参数请求字符串,将把上一步排序好的请求参数格式化成“参数名称”=“参数值”的形式
- 注意:“参数值”为原始值而非url编码后的值。然后将格式化后的各个参数用"&"拼接在一起
- 最终生成的 query 字符串为:
appid=tpidGFSJgefA&nonce=93914207×tamp=1615789882
4.拼接签名前原文字符串
此步骤生成签名原文字符串
签名原文字符串由以下几个参数构成: 请求方法: 支持 POST 和 GET 方式,注意方法为全大写。 请求域名: open.wesurvey.com/api/user/ 。实际的请求域名根据接口所属模块的不同而不同,详见各接口说明。 请求字符串: 即上一步 URL参数排序后拼接生成的请求字符串。
GET请求签名原文串的拼接规则: "GET" + 请求域名 + 接口路由 + "?" + query字符串
GETopen.wesurvey.com/api/signature/check?appid=tpidGFSJgefA&nonce=26377876×tamp=1615794722
- POST请求签名原文串的拼接规则: "POST" + 请求域名 + 接口路由 + "?" + query字符串 + "&data=" + 请求包体json字符串
POSTopen.wesurvey.com/api/signature/check?appid=tpidGFSJgefA&nonce=93914207×tamp=1615789882&data={"input":"ping"}
5.生成签名
首先使用HMAC-SHA1算法对上一步中获得的签名原文字符串进行签名,然后将生成的签名串使用十六进制进行编码,即可获得最终的签名串。 (各语言代码参考下方示例代码)
6.拼接签名
将第3步拼接后的字符串添加来自第5步的sign参数,如 xxx&sign={$signStr}。最终请求地址为:
https://open.wesurvey.com/api/signature/check?appid=tpidGFSJgefA&nonce=26377876×tamp=1615794722&sign=a6d9017202db28fedc622a40833d794452179c45
7.发起请求
POST
https://open.wesurvey.com/api/signature/check?appid=tpidGFSJgefA&nonce=93914207×tamp=1615789882&sign=a6d9017202db28fedc622a40833d794452179c45
{"input":"ping"}
示例代码
PHP示例代码
<?php
$appid = "tpidGFSJgefA"; // 替换成自己的 appid
$secret = "ff47fd770c11936a14435c2a8f15fa6626c90464"; // 替换成自己的 secret
$data = json_encode(["input"=>"ping"]);
$method = "POST";
$url = "open.wesurvey.com/api/signature/check";
$params = [
"timestamp" => time(),
"appid" => $appid,
"nonce" => rand(1, 100000000),
];
// 1. 参数排序
ksort($params);
$paramsStr = http_build_query($params);
$rawStr = $method . $url . "?" . $paramsStr;
if ($method == "POST" || $method == "PUT") {
$rawStr = $rawStr . "&data=" . $data;
}
// 2. hmac_sha1 签名
$key = utf8_encode($secret);
$msg = utf8_encode($rawStr);
// 3. 十六进制字符串
$b16encoded = hash_hmac("sha1", $msg, $key, false);
// 4. urlencode
$signStr = urlencode($b16encoded);
// 5. 发送请求
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "https://".$url."?".$paramsStr."&sign=".$signStr);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/json'));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
var_dump($response);
?>
Golang示例代码
package main
import (
"bytes"
"crypto/hmac"
"crypto/sha1"
"encoding/hex"
"encoding/json"
"fmt"
"math/rand"
"net/http"
"net/url"
"sort"
"strconv"
"time"
)
func main() {
appid := "tpidGFSJgefA" // 替换成自己的 appid
secret := "ff47fd770c11936a14435c2a8f15fa6626c90464" // 替换成自己的 secret
data, _ := json.Marshal(map[string]string{"input": "ping"})
method := "POST"
apiURL := "open.wesurvey.com/api/signature/check"
params := map[string]string{
"timestamp": strconv.FormatInt(time.Now().Unix(), 10),
"appid": appid,
"nonce": strconv.Itoa(rand.Intn(100000000)),
}
// 1. 参数排序
keys := make([]string, 0, len(params))
for k := range params {
keys = append(keys, k)
}
sort.Strings(keys)
paramsStr := ""
for _, k := range keys {
paramsStr += k + "=" + url.QueryEscape(params[k]) + "&"
}
paramsStr = paramsStr[:len(paramsStr)-1]
rawStr := method + apiURL + "?" + paramsStr
if method == "POST" || method == "PUT" {
rawStr = rawStr + "&data=" + string(data)
}
// 2. hmac_sha1 签名
key := []byte(secret)
mac := hmac.New(sha1.New, key)
mac.Write([]byte(rawStr))
b16encoded := hex.EncodeToString(mac.Sum(nil))
// 4. urlencode
signStr := url.QueryEscape(b16encoded)
// 打印签名字符串
fmt.Println(signStr)
// 5. 发送请求
resp, err := http.Post("https://"+apiURL+"?"+paramsStr+"&sign="+signStr, "application/json", bytes.NewReader(data))
if err != nil {
fmt.Println(err.Error())
return
}
defer resp.Body.Close()
fmt.Println(resp.Status)
// 5. 解析请求结果
type respStruct struct {
Code string `json:"code"`
RequestID string `json:"request_id"`
}
var respData respStruct
err = json.NewDecoder(resp.Body).Decode(&respData)
if err != nil {
fmt.Println(err.Error())
return
}
fmt.Println(respData)
}
Java示例代码
import java.io.IOException;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.stream.Collectors;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
public class Main {
public static void main(String[] args) throws IOException, NoSuchAlgorithmException, InvalidKeyException {
testSign();
}
public static void testSign() throws IOException, NoSuchAlgorithmException, InvalidKeyException {
String appid = "tpidGFSJgefA"; // 替换成自己的 appid
String secret = "ff47fd770c11936a14435c2a8f15fa6626c90464"; // 替换成自己的 secret
// Manually creating JSON string
String data = "{\"input\":\"ping\"}";
String method = "POST";
String url = "open.wesurvey.com/api/signature/check";
Map<String, Object> params = new HashMap<>();
params.put("timestamp", System.currentTimeMillis() / 1000L);
params.put("appid", appid);
params.put("nonce", new Random().nextInt(100000000));
// 1. 参数排序
String paramsStr = params.entrySet()
.stream()
.sorted(Map.Entry.comparingByKey())
.map(entry -> entry.getKey() + "=" + entry.getValue())
.collect(Collectors.joining("&"));
String rawStr = method + url + "?" + paramsStr;
if ("POST".equals(method) || "PUT".equals(method)) {
rawStr = rawStr + "&data=" + data;
}
// 2. hmac_sha1 签名
Mac mac = Mac.getInstance("HmacSHA1");
SecretKeySpec secretKeySpec = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA1");
mac.init(secretKeySpec);
byte[] hmacSha1Bytes = mac.doFinal(rawStr.getBytes(StandardCharsets.UTF_8));
// 3. 十六进制字符串
StringBuilder sb = new StringBuilder();
for (byte b : hmacSha1Bytes) {
sb.append(String.format("%02x", b));
}
String b16encoded = sb.toString();
// 4. urlencode
String signStr = java.net.URLEncoder.encode(b16encoded, StandardCharsets.UTF_8.toString());
// 5. 发送请求
URL requestUrl = new URL("https://" + url + "?" + paramsStr + "&sign=" + signStr);
HttpURLConnection conn = (HttpURLConnection) requestUrl.openConnection();
conn.setRequestMethod("POST");
conn.setRequestProperty("Content-Type", "application/json");
conn.setDoOutput(true);
try (OutputStream os = conn.getOutputStream()) {
os.write(data.getBytes(StandardCharsets.UTF_8));
os.flush();
}
int responseCode = conn.getResponseCode();
String responseMessage = new java.io.BufferedReader(new java.io.InputStreamReader(conn.getInputStream()))
.lines()
.collect(Collectors.joining("\n"));
System.out.println("Response Code: " + responseCode);
System.out.println("Response Message: " + responseMessage);
}
}
Python3示例代码
import time
import random
import hmac
import hashlib
import urllib.parse
import requests
import json
def test_sign():
appid = "tpidGFSJgefA" # 替换成自己的 appid
secret = "ff47fd770c11936a14435c2a8f15fa6626c90464" # 替换成自己的 secret
data = json.dumps({"input": "ping"})
method = "POST"
url = "open.wesurvey.com/api/signature/check"
params = {
"timestamp": int(time.time()),
"appid": appid,
"nonce": random.randint(1, 100000000),
}
# 1. 参数排序
sorted_params = sorted(params.items())
params_str = urllib.parse.urlencode(sorted_params)
raw_str = method + url + "?" + params_str
if method in ["POST", "PUT"]:
raw_str += "&data=" + data
# 2. hmac_sha1 签名
key = secret.encode('utf-8')
msg = raw_str.encode('utf-8')
b16encoded = hmac.new(key, msg, hashlib.sha1).hexdigest()
# 4. urlencode
sign_str = urllib.parse.quote(b16encoded)
# 5. 发送请求
full_url = f"https://{url}?{params_str}&sign={sign_str}"
headers = {'Content-Type': 'application/json'}
response = requests.post(full_url, data=data, headers=headers)
print(response.text)
# 调用函数进行测试
test_sign()
请求结果示例
- 请求成功
{
"code": "OK",
"error": {
"type": ""
},
"data": {
"output": "pong"
},
"request_id": "7a1eeb3c-3225-404e-9f60-558e58fc5ed9"
}
- 请求失败
{
"code": "PermissionDenied",
"error": {
"type": "invalid_signature"
},
"data": {},
"request_id": "ec8917cb-e7ee-4462-8423-abffb27641c5"
}
错误码
一级错误状态
code 错误代码 | 错误描述 |
---|---|
OK | 请求成功 |
PermissionDenied | 请求失败(无权限) |
二级错误状态
error.type 错误代码 | 错误描述 |
---|---|
invalid_appid | appid 错误 |
invalid_signature | 签名错误 |
timestamp_error | 时间戳错误 |
nonce_existed | 短期内 nonce 重复 |
claim_error | 资源权限错误 |