跳到主要内容

接口签名鉴权

该鉴权方式支持合作平台使用开放平台接口

接口域名

https://open.wesurvey.com

请求结构

  1. API 的所有接口均通过 HTTPS 进行通信,均使用 UTF-8 编码
  2. 支持的 HTTP 请求方法:POST、GET、PUT、DELETE
  3. POST 请求支持的 Content-Type 类型: application/json ,后续文档中参数位置为 body 则放在 json 传入
  4. 注意:请勿在前端直接发起开放接口请求,防止泄露 secret

公共参数

1. 参数说明

用于标识用户和接口鉴权目的的参数,如非必要,在每个接口单独的接口文档中不再对这些参数进行说明,但每次请求均需要携带这些参数,才能正常发起请求。 公共参数定义如下:

参数名描述参考值
appid第三方应用idtpidGFSJgefA
timestamp当前时间戳1615783862
nonce随机正整数815390
sign签名ff47fd770c11936a14435c2a8f15fa6626c90464

2.GET 请求结构示例

(该接口用于签名方法调试,无实际用途)
https://open.wesurvey.com/api/signature/check?appid=tpidGFSJgefA&nonce=26377876&timestamp=1615794722&sign=70e45a2659340a9d97e1115483e265c0a1e3a207

3.POST 请求结构示例

(该接口用于签名方法调试,无实际用途)
https://open.wesurvey.com/api/signature/check?appid=tpidGFSJgefA&nonce=83990929&timestamp=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&timestamp=1615789882

4.拼接签名前原文字符串

  • 此步骤生成签名原文字符串

  • 签名原文字符串由以下几个参数构成: 请求方法: 支持 POST 和 GET 方式,注意方法为全大写。 请求域名: open.wesurvey.com/api/user/ 。实际的请求域名根据接口所属模块的不同而不同,详见各接口说明。 请求字符串: 即上一步 URL参数排序后拼接生成的请求字符串。

  • GET请求签名原文串的拼接规则: "GET" + 请求域名 + 接口路由 + "?" + query字符串

GETopen.wesurvey.com/api/signature/check?appid=tpidGFSJgefA&nonce=26377876&timestamp=1615794722
  • POST请求签名原文串的拼接规则: "POST" + 请求域名 + 接口路由 + "?" + query字符串 + "&data=" + 请求包体json字符串
POSTopen.wesurvey.com/api/signature/check?appid=tpidGFSJgefA&nonce=93914207&timestamp=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&timestamp=1615794722&sign=a6d9017202db28fedc622a40833d794452179c45

7.发起请求

POST
https://open.wesurvey.com/api/signature/check?appid=tpidGFSJgefA&nonce=93914207&timestamp=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_appidappid 错误
invalid_signature签名错误
timestamp_error时间戳错误
nonce_existed短期内 nonce 重复
claim_error资源权限错误