前言
作为一个后端开发,提供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快乐,欢迎持续关注"安前码后",一个注重工作干货的号。