OpenAPI的签名校验

lxf2023-05-21 01:12:09

前言

作为一个后端开发,提供API接口或者对接第三方API接口的时候,考虑接口的防刷、重放等安全问题,严格意义上,都需要加上双方约定的签名规则。

大致思路

一般情况下,签名规则没有墨守成规的规定,只要考虑过期性、安全性即可,不用把他想的很高大上。

事先准备

准备API KEY和秘钥API SECRET,这个通常由API提供方自己生产,不固定,只要保证不泄露即可。

一、时间戳(timestamp)

生成一个Unix时间戳timestamp,时间精确到毫秒【即1970年1月1日(UTC/GMT的午夜)开始所经过的毫秒数】;实现方式如下:

 String timestamp = Long.toString(System.currentTimeMillis()); 

二、生成随机字符(nonce)

生成随机数nonce(注:目前定义的是32位的,可以通过随机数工具类生成) ;实现方式比如:

String nonce = RandomStringUtils.randomAlphanumeric(32);

三、生成签名signature

1)将timestamp、nonce、API_KEY 这三个字符串依据“字符串首位字符的ASCII码”进行升序排列(排序过程中若出现ASCII码值相同的情况,则依次递增对下一位进行比较)(这种排序,”也就是俗称的字典序“),并将排序后的结果拼接成为一个字符串join_str; 2)接下来在用API_SECRET对上面生成这个字符串join_str做hmac-sha256 签名,(其他加密方式也可以,没有固定)并且以16进制编码,得到signature;

四、参数拼接

将上述得到的timestamp,nonce,signature,与 API_KEY按照 #{k}=#{v}并以 ',' 为区分拼接在一起形成新的字符串,这就是要返回签名认证字符串authorization;也可以不拼接,直接将timestamp、nonce、signature三个参数放入HTTP请求头即可,形式不唯一。

关键代码演示

将timestamp、nonce、API_KEY 这三个字符串依据“字符串首位字符的ASCII码”进行升序排列(排序过程中若出现ASCII码值相同的情况,则依次递增对下一位进行比较),并join成一个字符串

public static String genOriString(String timestamp,String nonce,String API_KEY){
 
        ArrayList<String> beforesort = new ArrayList<String>();
        beforesort.add(API_KEY);
        beforesort.add(timestamp);
        beforesort.add(nonce);
 
        Collections.sort(beforesort, new SpellComparator());
        StringBuffer aftersort = new StringBuffer();
        for (int i = 0; i < beforesort.size(); i++) {
            aftersort.append(beforesort.get(i));
        }
 
        String join_str = aftersort.toString();
        return join_str;
    }

用API_SECRET对join_str做hmac-sha256签名,且以16进制编码,返回

public static String genEncryptString(String join_str, String API_SECRET){
 
        Key sk = new SecretKeySpec(API_SECRET.getBytes(), "HmacSHA256");
        Mac mac = Mac.getInstance(sk.getAlgorithm());
        mac.init(sk);
        final byte[] hmac = mac.doFinal(join_str.getBytes());//完成hmac-sha256签名
        StringBuilder sb = new StringBuilder(hmac.length * 2);
        Formatter formatter = new Formatter(sb);
            for (byte b : hmac) {
                formatter.format("%02x", b);
            }
        String signature = sb.toString();//完成16进制编码
        return signature;
    }

将上述的值按照 #{k}=#{v} 并以 ',' join在一起,返回签名认证字符串:

public static String genauthorization(String API_KEY, String timestamp, String nonce, String signature){
 
        String authorization = "key=" + API_KEY
                     +",timestamp=" + timestamp
                         +",nonce=" + nonce
                     +",signature=" + signature;
        return authorization;
    }

SpringBoot的pom依赖

     <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.12.0</version>
     </dependency>
 
     <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpmime</artifactId>
            <version>RELEASE</version>
            <scope>compile</scope>
     </dependency>

SpellComparator 类

 
import java.util.Comparator;
 
 
public class SpellComparator implements Comparator<Object> {
    public int compare(Object o1, Object o2) {
        try{
            String s1 = new String(o1.toString().getBytes("GB2312"), "ISO-8859-1");
            String s2 = new String(o2.toString().getBytes("GB2312"), "ISO-8859-1");
            return s1.compareTo(s2);
        }catch (Exception e){
            e.printStackTrace();
        }
        return 0;
     }
}

完整生成的Demo

import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.SignatureException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Formatter;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.http.client.ClientProtocolException;
 
 
public class GenerateString {
    // 开放平台注册获取id(API KEY)
    public static final String id = "22bfe9745135";
    // 开放平台注册获取密钥(API SECRET)
    public static final String secret = "19fbdc10";
 
    private static final String HASH_ALGORITHM = "HmacSHA256";
    static String timestamp = Long.toString(System.currentTimeMillis());
    static String nonce = RandomStringUtils.randomAlphanumeric(32);
 
    public static String genOriString(String api_key){
 
        ArrayList<String> beforesort = new ArrayList<String>();
        beforesort.add(api_key);
        beforesort.add(timestamp);
        beforesort.add(nonce);
 
        Collections.sort(beforesort, new SpellComparator());
        StringBuffer aftersort = new StringBuffer();
        for (int i = 0; i < beforesort.size(); i++) {
            aftersort.append(beforesort.get(i));
        }
 
        String OriString = aftersort.toString();
        return OriString;
    }
 
    public static String genEncryptString(String genOriString, String api_secret)throws SignatureException {
        try{
            Key sk = new SecretKeySpec(api_secret.getBytes(), HASH_ALGORITHM);
            Mac mac = Mac.getInstance(sk.getAlgorithm());
            mac.init(sk);
            final byte[] hmac = mac.doFinal(genOriString.getBytes());
            StringBuilder sb = new StringBuilder(hmac.length * 2);
 
            @SuppressWarnings("resource")
            Formatter formatter = new Formatter(sb);
            for (byte b : hmac) {
                formatter.format("%02x", b);
            }
            String EncryptedString = sb.toString();
            return EncryptedString;
        }catch (NoSuchAlgorithmException e1){
            throw new SignatureException("error building signature, no such algorithm in device "+ HASH_ALGORITHM);
        }catch (InvalidKeyException e){
            throw new SignatureException("error building signature, invalid key " + HASH_ALGORITHM);
        }
    }
 
    public static String genHeaderParam(String api_key, String api_secret) throws SignatureException{
 
        String GenOriString = genOriString(api_key);
        String EncryptedString = genEncryptString(GenOriString, api_secret);
 
        String HeaderParam = "key=" + api_key
                +",timestamp=" + timestamp
                +",nonce=" + nonce
                +",signature=" + EncryptedString;
        System.out.println(HeaderParam);
        return HeaderParam;
    }
 
    public static void main(String[] args) throws ClientProtocolException, IOException, SignatureException{
        String s = genHeaderParam(id, secret);
        System.out.println(s);
    }
}

写在最后

这个是参考一位兄弟的博文整理的,确实挺不错,简单实用,至于接口签名校验,就把调用方的几个参数进行校验生成签名sign,比对两个签名sign和对方常来的signature是否一致即可,最好对传来的时间戳进行校验,可以设置10分钟有效。 好了,今天的分享就到这,兄弟姐妹们,520快乐,欢迎持续关注"安前码后",一个注重工作干货的号。

OpenAPI的签名校验

本网站是一个以CSS、JavaScript、Vue、HTML为核心的前端开发技术网站。我们致力于为广大前端开发者提供专业、全面、实用的前端开发知识和技术支持。 在本网站中,您可以学习到最新的前端开发技术,了解前端开发的最新趋势和最佳实践。我们提供丰富的教程和案例,让您可以快速掌握前端开发的核心技术和流程。 本网站还提供一系列实用的工具和插件,帮助您更加高效地进行前端开发工作。我们提供的工具和插件都经过精心设计和优化,可以帮助您节省时间和精力,提升开发效率。 除此之外,本网站还拥有一个活跃的社区,您可以在社区中与其他前端开发者交流技术、分享经验、解决问题。我们相信,社区的力量可以帮助您更好地成长和进步。 在本网站中,您可以找到您需要的一切前端开发资源,让您成为一名更加优秀的前端开发者。欢迎您加入我们的大家庭,一起探索前端开发的无限可能!