签名生成通用步骤
1将所有发送的数据设为集合A
2将集合A内非空参数值的参数按照参数名ASCII码从小到大排序(字典序),使用URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串$queryString。
3按照指定规则加密算出sign值
特别注意以下重要规则:
●参数名ASCII码从小到大排序(字典序);
●如果参数的值为空不参与签名;
●参数名区分大小写;
●传送的sign参数不参与签名,将生成的签名与该sign值作校验。
●sign签名失效时间为2分钟
●open_app_sign签名失效时间为2分钟
sign签名加密算法
$sign = md5($queryString . $apiKey . base64_encode($timestamp . $apiKey . $queryString) . $nonceStr);
open_app_sign签名加密算法
$sign = md5('913702023503242914' . $openAppId . $queryString . md5($timestamp . $openAppApiKey . $queryString) . $nonceStr)
举例
假设请求参数如下:
id: 10
name: test
第一步:对参数按照key=value的格式,并按照参数名ASCII字典序排序如下:
id=10&name=test
第二步:计算签名(以sign为例):
$sign = md5("id=10&name=test" . "ucPFmeGuuTMh1t8BAsTFdztlJDKRJeGs" . base64_encode(1609754777 . "ucPFmeGuuTMh1t8BAsTFdztlJDKRJeGs" . "id=10&name=test") . 1609754777);
最终得到最终发送的数据:
cc115a7c187f061dce2b2d3c4cb1eed3
各语言请求示例
PHP请求示例
// 当前时间戳
$timestamp = time();
// 商城V5 API_KEY
$apiKey = 'V5TEST';
// 开放应用APP_ID
$openAppId = 100001;
// 开放应用API_KEY
$openAppApiKey = 'V9cfFexSl7ka79k7VM2L95XQNNxk7hAP';
// 所有请求参数
$params = [
// 公共请求参数
'api_key' => $apiKey,
'timestamp' => $timestamp,
'open_app_id' => $openAppId,
'nonce_str' => StringHelper::random(32),// mLqpaOIV3igna9dPTfSKuxhZPv5lcKQS
// 接口请求参数
'uniacid' => 2,
];
// 字典排序
ksort($params);
$queryString = '';
foreach ($params as $key => $value) {
if (is_array($value)) {
$value = json_encode($value);
}
$queryString .= '&' . ($key . '=' . $value);
}
// 转为字符串
$queryString = substr($queryString, 1); // api_key=V5TEST&open_app_id=100001×tamp=1609818812&uniacid=2
// 计算签名
$params['sign'] = md5($queryString . $apiKey . base64_encode($timestamp . $apiKey . $queryString) . $params['nonce_str']); // 8c7c488aff05f21230053e7db204697e
$params['open_app_sign'] = md5('913702023503242914' . $openAppId . $queryString . md5($timestamp . $openAppApiKey . $queryString) . $params['nonce_str']); // c61e2d4d96ffb1452cc6f2603b3dbef1
// 调用接口
$res = HttpHelper::get('https://www.domain.com/addons/renren_shop/public/index.php?r=client/apps/openApi/shop/get-shop-id&' . http_build_query($params), [
'headers' => [
'shop-id' => 2,
]
]);
var_dump($res); // string(23) "{"error":0,"shop_id":2}"
Python请求示例
import base64
import hashlib
import string
import time
from random import randint
from typing import Dict
import requests
# 随机字符串
def random_str(long=32):
base = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
result = ""
for i in range(long):
result += base[randint(0, len(base) - 1)]
return result
# 封装md5
def md5(text: string):
hl = hashlib.md5()
hl.update(text.encode(encoding='utf-8'))
return hl.hexdigest()
# 字典排序
def k_sort_str(d: Dict[str, str]):
d_keys = list(d.keys())
d_keys.sort()
result = ""
for k in d_keys:
result += f"{k}={d[k]}"
if k != d_keys[-1]:
result += '&'
return result
# 演示请求
def demo_request():
# 定义当前时间戳
timestamp = str(int(time.time()))
# 商城V5店铺创建的API_KEY
api_key = 'V5suyZ45GcSyIURW2kPqVpQyQqC0Vx1z'
# 开放应用的APP_ID
open_app_id = '100001'
# 开放应用的APP_API_KEY
open_app_api_key = 'V9cfFexSl7ka79k7VM2L95XQNNxk7hAP'
# 请求接口地址
url = 'https://yousitedomain.com/wap/3/api/apps/openApi/user/index/check-password'
# 请求参数Dict
request_params = {
# 公共请求参数
'api_key': api_key,
'timestamp': timestamp,
'open_app_id': open_app_id,
'nonce_str': random_str(),
# 业务参数
'username': 'admin',
'password': 'admin',
}
# 字典排序后转为字符串
query_string = k_sort_str(request_params)
# 计算sign中的base64值
base64_str = str(timestamp) + api_key + query_string
base64_encode_str = base64.b64encode(base64_str.encode('utf-8')).decode('utf-8')
# 计算open_app_sign中的md5值
md5_encode = md5(timestamp + open_app_api_key + query_string)
# 计算签名
request_params.update({
'sign': md5(query_string + api_key + base64_encode_str + request_params['nonce_str']),
'open_app_sign': md5(
'913702023503242914' + open_app_id + query_string + md5_encode + request_params['nonce_str']),
})
# 执行请求
response = requests.request('POST', url, headers={}, data=request_params, files=[])
# 返回信息
print(response.text)
# 执行demo
demo_request()
Golang请求示例
package main
import (
"bytes"
"crypto/md5"
"encoding/base64"
"encoding/hex"
"fmt"
"io/ioutil"
"math/rand"
"mime/multipart"
"net/http"
"sort"
"strconv"
"strings"
"time"
)
func main() {
// 商城V5店铺创建的API_KEY
apiKey := "V5suyZ45GcSyIURW2kPqVpQyQqC0Vx1z"
// 开放应用的APP_ID
openAppID := "100001"
// 开放应用的APP_API_KEY
openAppApiKey := "Aojtd3ZwUdfuoNu6joJ6DuZ96O6FID9T"
# 请求接口地址
url := "https://yousitedomain.com/wap/3/api/apps/openApi/user/index/check-password"
// 定义当前时间戳
timestamp := timestampStr()
// 请求参数
requestParams := map[string]string{
// 公用请求参数
"api_key": apiKey,
"timestamp": timestamp,
"open_app_id": openAppID,
"nonce_str": randomStr(32),
// 业务请求参数
"username": "admin",
"password": "admin",
}
// 字典排序后转为字符串
queryString := kSort2Str(requestParams)
// 计算sign中的base64值
bas64Str := base64.StdEncoding.EncodeToString([]byte(timestamp + apiKey + queryString))
// 计算open_app_sign中的md5值
md5Encode := md5Str(timestamp + openAppApiKey + queryString)
// 计算签名
requestParams["sign"] = md5Str(queryString + apiKey + bas64Str + requestParams["nonce_str"])
requestParams["open_app_sign"] = md5Str("913702023503242914" + openAppID + queryString + md5Encode + requestParams["nonce_str"])
// 以下开始请求!!! 可以换成自己的httpClient
res, err := request(url, requestParams)
if err != nil {
panic(err)
}
// 打印返回结果
fmt.Println(res)
}
// 执行请求(POST)
// url 请求的地址
// params 请求的参数
func request(url string, params map[string]string) (resStr string, err error) {
payload := &bytes.Buffer{}
writer := multipart.NewWriter(payload)
// 追加请求参数
for k, v := range params {
_ = writer.WriteField(k, v)
}
err = writer.Close()
if err != nil {
return
}
client := &http.Client{}
req, err := http.NewRequest("POST", url, payload)
if err != nil {
return
}
req.Header.Set("Content-Type", writer.FormDataContentType())
res, err := client.Do(req)
if err != nil {
return
}
defer func() {
_ = res.Body.Close()
}()
body, err := ioutil.ReadAll(res.Body)
if err != nil {
return
}
resStr = string(body)
return
}
// 获取当前时间戳(转为string)
func timestampStr() string {
now := time.Now().Unix()
return strconv.FormatInt(now, 10)
}
// 字典排序
// params 要排序的参数
func kSort2Str(params map[string]string) string {
// ksort
var keys []string
for k, _ := range params {
keys = append(keys, k)
}
sort.Strings(keys)
var str string
// 拼接
for _, k := range keys {
if k == "sign" {
continue
}
str = str + k + "=" + params[k] + "&"
}
return str[0 : len(str)-1]
}
// 计算字符串的Md5
// string 要计算的字符串
func md5Str(string string) string {
md := md5.New()
md.Write([]byte(string))
cSign := hex.EncodeToString(md.Sum(nil))
return cSign
}
// 随机字符串
// length 要随机的长度
func randomStr(length int) string {
// 字符
chars := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
sLen := strings.Count(chars, "") - 1
str := ""
for length > 0 {
random := rand.Intn(sLen)
str += string(chars[random])
length--
}
return str
}
Nodejs请求示例
const request = require('request')
const md5 = require('md5-node')
/**
* base64加密
* @param content 要加密的内容
* @returns {string}
* @author likexin
*/
function base64Encode(content) {
return Buffer.from(content).toString('base64')
}
/**
* 随机生成字符串
* @param len 生成的长度
* @returns {string}
* @author likexin
*/
function randomStr(len = 32) {
// 字符
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
// 字符长度
const sLen = chars.length
// 待生成的字符串
let str = ''
for (let i = 0; i < len; i++) {
str += chars.charAt(Math.floor(Math.random() * sLen))
}
return str
}
/**
* 字典排序后转为字符串
* @param params
* @returns {string}
* @author likexin
*/
function kSort2Str(params) {
let keys = Object.keys(params)
keys.sort()
const kLen = keys.length
let str = ''
keys.forEach((k) => {
str += k + "=" + params[k]
if (k !== keys[kLen - 1]) {
str += '&'
}
})
return str
}
/**
* 演示请求
* @author likexin
*/
function demoRequest() {
// 商城V5店铺创建的API_KEY
const apiKey = 'V5szyZ35GcSyIURW2kPqVpQ1QqC0Vxzx'
// 开放应用的APP_ID
const openAppId = '900030'
// 开放应用的APP_API_KEY
const openAppApiKey = 'Aojtd3Zwwdfuo2u6joJ6DuZ96O6FID31'
// 接口地址
const apiUrl = 'https://yoursitedomain.com/wap/1223/api/apps/openApi/user/index/check-password'
// 定义当前时间戳
const timestamp = Math.floor(Date.now() / 1000);
// 请求参数
let requestParams = {
// 公用请求参数
'api_key': apiKey,
'timestamp': timestamp,
'open_app_id': openAppId,
'nonce_str': randomStr(32),
// 业务请求参数
'username': 'admin',
'password': 'admin',
}
// 字典排序后转为字符串
const queryString = kSort2Str(requestParams)
// 计算签名
requestParams['sign'] = md5(queryString + apiKey + base64Encode(timestamp + apiKey + queryString) + requestParams['nonce_str']);
requestParams['open_app_sign'] = md5('913702023503242914' + openAppId + queryString + md5(timestamp + openAppApiKey + queryString) + requestParams['nonce_str']);
// 下面开始请求,可以换成自己的httpClient
request({
'method': 'POST',
'url': apiUrl,
'headers': {},
formData: requestParams
}, (error, response) => {
if (error) {
console.error(error)
return
}
// 返回结果
console.log(response.body);
})
}
// 执行demo
demoRequest()
Java请求示例
package cn.likexin.demo;
import okhttp3.*;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.util.Base64Utils;
import org.springframework.util.DigestUtils;
import java.io.IOException;
import java.util.Date;
import java.util.HashMap;
import java.util.Random;
import java.util.TreeMap;
@SpringBootApplication
public class DemoApplication {
/**
* 程序入口
* @param args 参数
* @throws IOException
* @author likexin
*/
public static void main(String[] args) throws IOException {
// SpringApplication.run(DemoApplication.class, args);
// 商城V5店铺创建的API_KEY
String apiKey = "V5suyZ45GcSyIURW2kPqVpQyQqC0Vx1z";
// 开放应用的APP_ID
String openAppID = "100001";
// 开放应用的APP_API_KEY
String openAppApiKey = "Aojtd3ZwUdfuoNu6joJ6DuZ96O6FID9T";
// 请求接口地址
String url = "https://yousitedomain.com/wap/3/api/apps/openApi/user/index/check-password";
// 定义当前时间戳
String timestamp = timestampStr();
// 定义请求参数为HashMap
HashMap
// 公用请求参数
requestParams.put("api_key", apiKey);
requestParams.put("timestamp", timestamp);
requestParams.put("open_app_id", openAppID);
requestParams.put("nonce_str", randomStr(32));
// 业务参数
requestParams.put("username", "admin");
requestParams.put("password", "admin");
// 字典排序后转为字符串
String queryString = kSort2Str(requestParams);
// 计算sign中的base64值
String bas64Str = base64Str(timestamp + apiKey + queryString);
// 计算open_app_sign中的md5值
String md5Encode = md5Str(timestamp + openAppApiKey + queryString);
// 计算签名
requestParams.put("sign", md5Str(queryString + apiKey + bas64Str + requestParams.get("nonce_str")));
requestParams.put("open_app_sign", md5Str("913702023503242914" + openAppID + queryString + md5Encode + requestParams.get("nonce_str")));
// 以下开始请求!!! 可以换成自己的httpClient
OkHttpClient client = new OkHttpClient().newBuilder().build();
MultipartBody.Builder builder = new MultipartBody.Builder().setType(MultipartBody.FORM);
for (String key : requestParams.keySet()) {
builder.addFormDataPart(key, requestParams.get(key));
}
RequestBody body = builder.build();
Request request = new Request.Builder()
.url(url)
.method("POST", body)
.build();
Response response = client.newCall(request).execute();
// 打印接口返回结果
System.out.println(response.body().string());
}
/**
* 获取当前时间戳
*
* @return 当前时间戳(秒)
* @author likexin
*/
private static String timestampStr() {
return String.valueOf(new Date().getTime() / 1000);
}
/**
* 获取随机字符串
*
* @param length 长度
* @return String
* @author likexin
*/
private static String randomStr(int length) {
String str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
Random random = new Random();
StringBuffer sb = new StringBuffer();
for (int i = 0; i < length; i++) {
int number = random.nextInt(62);
sb.append(str.charAt(number));
}
return sb.toString();
}
/**
* 排序并且转为String
*
* @param params 所有参数
* @return 拼接后的字符串
* @author likexin
*/
private static String kSort2Str(HashMap
TreeMap
StringBuilder str = new StringBuilder();
for (String key : paramTreeMap.keySet()) {
str.append(key).append("=").append(paramTreeMap.get(key)).append("&");
}
return str.substring(0, str.length() - 1);
}
/**
* MD5
*
* @param str 要MD5的字符串
* @return MD5后的字符串
* @author likexin
*/
private static String md5Str(String str) {
return DigestUtils.md5DigestAsHex(str.getBytes());
}
/**
* Base64encode
*
* @param str 要encode的字符串
* @return encoded的字符串
* @author likexin
*/
private static String base64Str(String str) {
return Base64Utils.encodeToString(str.getBytes());
}
}