对外接口签名生成方式

接口签名生成方式

前言

当某个系统对外部系统提供接口访问时,为提高接口请求安全性,往往会在接口访问时添加签名,当外部系统访问本系统签名验证成功时才能正常返回数据,一般接口提供方会与外部系统提前约定好,不同外部系统用 appKey 加以区分,并且不同 appKey 对应不同秘钥(secretKey)

签名生成方式

以下以 Get 请求为例:

  1. 第一步:在请求参数中添加 appKey 和时间戳 timestamp,将所有请求参数(除了 sign )按照字母排序
  2. 第二步:使用&将第一步参数拼接成如下形式( k1=v1&k2=v2&k3=v3 … ),并且将秘钥(secretKey)拼接在最后,最终字符串为 k1=v1&k2=v2&k3=v3secretKey
  3. 第三步:将第二步中的字符串使用MD5加密生成签名(sign )
  4. 第四步:将签名(sign )作为入参传入

以Java代码为例

定义两个项目,sign-provider 为接口提供方,sign-consumer 调用 sign-provider 提供的接口

sign-provider

接口提供方,提供接口为:
http://127.0.0.1:9091/provider/hello?query=2&offset=0&limit=10&appKey=A&sign={{sign}}&timestamp={{timestamp}}

其中 appKey 参数非固定传值,此处假设接口提供方与 sign-consumer 约定其 appKey 为 A ,secretKey(秘钥) 为 123456,sign 由接口调用方 sign-consumer 根据入参和秘钥拼接并通过MD5加密生成,具体规则看 签名生成方式

具体代码
package com.example.signprovider;

import lombok.extern.slf4j.Slf4j;
import org.springframework.util.Assert;
import org.springframework.util.DigestUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.lang.reflect.Field;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.StringJoiner;
import java.util.TreeMap;

@Slf4j
@RestController
@RequestMapping("/provider")
public class HelloController {

    private static final long EXPIRE_TIME = 5;

    /**
     * 不同系统对应不同 appKey 和 secretKey
     */
    private static final Map<String, String> APP_KEY_MAP = new HashMap<>();

    static {
        APP_KEY_MAP.put("A", "123456");
    }

    @GetMapping("/hello")
    public String hello(RequestBean requestBean) {
        //获取客户端 appKey
        String appKey = requestBean.getAppKey();
        Assert.isTrue(APP_KEY_MAP.containsKey(appKey), "无效appKey!");
        //客户端传入的签名
        String requestSign = requestBean.getSign();
        //检查有无传入签名
        Assert.hasText(requestSign, "无效签名!");
        long requestTime = requestBean.getTimestamp();
        //如果请求发起时间与当前时间超过expireTime,则接口请求过期
        Assert.isTrue(System.currentTimeMillis() / 1000 - requestTime <= EXPIRE_TIME, "请求过期!");
        //生成签名
        String sign = "";
        try {
            sign = getSign(requestBean, APP_KEY_MAP.get(appKey));
        } catch (IllegalAccessException e) {
            e.printStackTrace();
            throw new RuntimeException("获取签名失败!");
        }
        //比对签名与传入签名是否一致
        Assert.isTrue(requestSign.equals(sign), "无效签名!");
        return "接口调用成功:" + requestBean;
    }

    private String getSign(RequestBean requestBean, String secretKey) throws IllegalAccessException {
        Map<String, Object> map = new TreeMap<>(String::compareTo);
        Field[] fields = requestBean.getClass().getDeclaredFields();
        for (Field field : fields) {
            if (!"sign".equals(field.getName())) {
                field.setAccessible(true);
                map.put(field.getName(), field.get(requestBean));
            }
        }
        StringJoiner stringJoiner = new StringJoiner("&");
        map.forEach((k, v) -> stringJoiner.add(k + "=" + v));
        log.debug("stringJoiner:" + stringJoiner);
        String paramStr = stringJoiner + secretKey;
        //MD5加密
        return DigestUtils.md5DigestAsHex(paramStr.getBytes(StandardCharsets.UTF_8));
    }

}

如上:使用的MD5加密方法为 spring 提供的工具类 org.springframework.util.DigestUtils

sign-consumer

sign-provider 对外提供 API 为:
http://127.0.0.1:9091/provider/hello?query=2&offset=0&limit=10&appKey=A&sign={{sign}}&timestamp={{timestamp}}
其中 appKey、sign、timestamp 等参数均可由系统内部提供,所以 sign-consumer 对外提供接口为:
http://127.0.0.1:9092/consumer/hello?query=2&offset=0&limit=10

具体代码
package com.example.signconsumer;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.util.DigestUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.StringJoiner;
import java.util.TreeMap;

@Slf4j
@RestController
@RequestMapping("/consumer")
public class ConsumerController {

    private static final String APP_KEY = "A";
    private static final String SECRET_KEY = "123456";

    private static final String URL = "http://127.0.0.1:9091/provider/hello";

    @Autowired
    private RestTemplate restTemplate;

    @GetMapping("/hello")
    public String hello(RequestBean requestBean) {
        //使用 TreeMap 可对key排序
        Map<String, Object> params = new TreeMap<>();
        params.put("appKey", APP_KEY);
        params.put("timestamp", System.currentTimeMillis() / 1000);
        params.put("limit", requestBean.getLimit());
        params.put("offset", requestBean.getOffset());
        params.put("query", requestBean.getQuery());
        //生成签名
        String sign = getSign(params);
        log.debug("sign:{}", sign);
        params.put("sign", sign);
        StringJoiner stringJoiner = new StringJoiner("&");
        params.forEach((k, v) -> stringJoiner.add(k + "=" + v));
        ResponseEntity<String> result = restTemplate.getForEntity(URL + "?" + stringJoiner, String.class);
        return result.getBody();
    }

    private String getSign(Map<String, Object> params) {
        StringJoiner stringJoiner = new StringJoiner("&");
        params.forEach((k, v) -> stringJoiner.add(k + "=" + v));
        log.debug("stringJoiner:" + stringJoiner);
        String paramStr = stringJoiner.toString() + SECRET_KEY;
        return DigestUtils.md5DigestAsHex(paramStr.getBytes(StandardCharsets.UTF_8));
    }
}

测试

浏览器调用 sign-consumer 提供的接口:

代码路径

https://github.com/husgithub/sign-test

git 地址:

git@github.com:husgithub/sign-test.git

通过 PostMan 测试

通过调用 sign-provider 提供的接口可以测试 sign-consumer 提供的功能是否正确,PostMan 提供编写脚本的能力,在 JS 脚本中我们可以生成 timestamp 、sign 参数的值

打开 PostMan 后定位到 Pre-request Script 栏,可在此写 JS 脚本:

脚本如下:

console.log("start......");
var timestamp = Math.floor(new Date().getTime()/1000);
pm.globals.set("timestamp", timestamp);
console.log("----");
console.log(request.url);
//console.log(pm.request.url.query.get("timestamp"));
var paramStr = request.url.split("?")[1];
console.log("url参数字符串为:"+paramStr);
console.log("分割字符串参数......");
var map = new Map();
var paramArr = paramStr.split("&");
for(var i=0;i<paramArr.length;i++){
    var p = paramArr[i].split("=");
    if("sign"!=(p[0])){
        if("timestamp"==p[0]){
            map.set(p[0],timestamp);
        }else{
            map.set(p[0],p[1]);
        }
    }
}
console.log(paramArr);
console.log(map.size);

//对map排序
var arrayObj = Array.from(map);
arrayObj.sort(function (a, b) {
    return a[0].localeCompare(b[0])
});
var sortParamStr = "";
for (var [key, value] of arrayObj) {
    console.log(key + ' = ' + value);
    sortParamStr += "&"+key+"="+value;
}
console.log(sortParamStr.substring(1));
//添加秘钥
var signStr = sortParamStr.substring(1)+"123456";
//生成签名
var sign = CryptoJS.MD5(signStr).toString();
console.log(sign);
pm.globals.set("sign", sign);

如下图:
通过 {{sign}} 的方式可以定义变量,之后可以通过 js 脚本对变量进行赋值

通过 request.url 可以获取请求 URL:

request.url

pm.globals.set(“sign”, sign); 表示对 {{sign}} 括号内的参数赋值:

pm.globals.set("sign", sign);

测试

通过 View -> Show Postman Console 可以打开 PostMan Console 控制台

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 微信支付接口签名是为了保证接口调用的安全性和完整性而生成的一串密文。下面是使用.Net生成微信支付接口签名的步骤: 1. 首先,需要准备以下参数:APPID(公众号或移动应用的唯一标识)、API密钥(微信商户平台设置的密钥)、随机字符串(用于防止重放攻击)、时间戳(接口调用的时间戳,10位的秒级时间戳即可)等。 2. 将上述参数按照字母顺序排序,并拼接成形如“key=value”的字符串,例如:"appid=xxx&mch_id=xxx&nonce_str=xxx&timestamp=xxx"。 3. 在拼接的字符串末尾加上"&key=商户的API密钥"。 4. 使用Hash算法对拼接的字符串进行签名,常用的Hash算法有MD5和SHA1。使用.Net生成签名的代码示例如下: ```csharp using System.Security.Cryptography; using System.Text; string signStr = "拼接的字符串" + "&key=商户的API密钥"; MD5 md5 = MD5.Create(); byte[] signBytes = md5.ComputeHash(Encoding.UTF8.GetBytes(signStr)); StringBuilder sb = new StringBuilder(); foreach (byte b in signBytes) { sb.AppendFormat("{0:x2}", b); } string sign = sb.ToString().ToUpper(); ``` 5. 最后得到的sign就是生成的微信支付接口签名,可以将其加入到支付接口的请求参数中进行发送。 需要注意的是,每次请求的随机字符串和时间戳应该是不同的,以确保签名的唯一性和安全性。生成签名的代码可以根据具体的.Net版本进行调整,上述示例仅为参考。 ### 回答2: 微信支付接口签名是通过对发送的数据进行加密生成的一个字符串,用于校验数据的完整性和真实性。下面是一个基本的生成微信支付接口签名的流程: 1. 首先,将所有发送给微信支付接口的参数按照字母序排序,除了sign参数本身,将所有参数的参数名和参数值用key=value的形式拼接成一个字符串。 2. 在拼接的字符串末尾加上商户的支付密钥,并将密钥和字符串进行MD5加密,生成一个32位的字符串。这里需要注意,商户支付密钥是在微信商户平台进行配置的,需要在生成签名时正确配置使用。 3. 将生成签名字符串赋值给sign参数,并将所有参数(包括sign参数)一起发送到微信支付接口。 4. 在接收到微信支付接口的响应时,需要对返回的数据进行校验。将返回的所有参数按照参数名的字母序排序,并按照key=value的形式拼接成一个字符串。 5. 同样地,在拼接的字符串末尾加上商户的支付密钥,进行MD5加密,生成一个32位的字符串。 6. 将生成签名字符串与返回的sign参数进行比较,如果一致,则表示数据完整和真实;如果不一致,则表示数据可能被篡改了。 通过上述的流程,可以保证微信支付接口安全和可靠性。需要注意的是,每个接口签名算法可能会有所不同,具体的签名规则和参数配置可以参考微信支付开发文档。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值