1.什么是接口签名?
答:接口开发是各系统之间对接的重要方式,其数据是通过开放的互联网传输,对数据的安全性要有一定要求。为了提高传输过程参数的防篡改性,签名sign的方式是目前比较常用的方式。
重点:请求身份是否合法?请求参数是否被恶意篡改,请求是否唯一.
2.怎么提供一个安全性高的接口?
答:
2.1. 请求身份:
Appkey(公钥)开发者标识确定唯一,AppSecret密钥(用于接口加密,确保生成参数不被猜测)有个缺点密钥让人家知道还是完蛋了.
2.2. 接口加参数
接口加个参数timestamp时间戳,格式为yyyy-mm-dd HH:mm:ss,例如:2013-05-06 13:52:03(建议传入当前时间),防止同一时间内点击两次.
接口加个参数Appkey(公钥).
接口加个参数sign签名字段:签名sign具体算法为:根据传入参数名称(签名sign除外)将所有请求参数按照首字母先后顺序排序,md5(密钥+除密钥外排好序的参数串+密钥)后转换为大写字母,注意编码,统一为utf8。注意要有AppSecret密钥.
类似下面这个样子:
method=xx
&appkey=xx
×tamp=2014-12-26 10:27:26
&format=json
&sign=生成的签名
以上两步,解决了防止防止篡改,参数正确,但是没有解决着重复请求参数伪造二次请求的隐患。也就是重放攻击。
2.3.timestamp+nonce方案
nonce指唯一的随机字符串,用来标识每个被签名的请求。通过为每个请求提供一个唯一的标识符,服务器能够防止请求被多次使用(记录所有用过的nonce以阻止它们被二次使用)。
然而,对服务器来说永久存储所有接收到的nonce的代价是非常大的。可以使用timestamp来优化nonce的存储。
假设允许客户端和服务端最多能存在6分钟的时间差,同时追踪记录在服务端的nonce集合。当有新的请求进入时,首先检查携带的timestamp是否在6分钟内,如超出时间范围,则拒绝,然后查询携带的nonce,如存在已有集合,则拒绝。否则,记录该nonce,并删除集合内时间戳大于6分钟的nonce(可以使用redis的expire,新增nonce的同时设置它的超时失效时间为6分钟)。
另外除了公钥密钥,还可以用token身份验证。
服务端验证账号密码,生成token,存储30分钟,token发给前端,前端每次请求带上这个token。但是仍然存在着安全隐患,token被劫持,伪造请求和篡改参数。
解决方法:Token+(AppKey参数加密),即使Token被劫持,对方不知道AppKey和签名算法,就无法伪造请求和篡改参数。再结合上述的重发攻击解决方案,即使请求参数被劫持也无法伪造二次重复请求。
系统可以设定一些规则:
- 公钥密钥有自己的系统生成.
- 接口有效性6分钟.
- 接口调用频率:1毫秒以上.
- 数据参数安全,验证签名.
- 具体算法为:根据传入参数名称(签名sign除外)将所有请求参数按照首字母先后顺序排序,md5(密钥+除密钥外排好序的参数串+密钥)后转换为大写字母。
还有就是排序参数,如果是只调用web接口不超过3个,建议不要写字母排序方法,浪费时间.
下面提供md5utils类:
使用: MD5Utils.MD5Encode(str,"utf8");
import java.security.MessageDigest;
public class MD5Utils {
private static final String hexDigIts[] = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"};
/**
* MD5加密
*
* @param origin 字符
* @param charsetname 编码
* @return
*/
public static String MD5Encode(String origin, String charsetname) {
String resultString = null;
try {
resultString = new String(origin);
MessageDigest md = MessageDigest.getInstance("MD5");
if (null == charsetname || "".equals(charsetname)) {
resultString = byteArrayToHexString(md.digest(resultString.getBytes()));
} else {
resultString = byteArrayToHexString(md.digest(resultString.getBytes(charsetname)));
}
} catch (Exception e) {
}
return resultString;
}
public static String byteArrayToHexString(byte b[]) {
StringBuffer resultSb = new StringBuffer();
for (int i = 0; i < b.length; i++) {
resultSb.append(byteToHexString(b[i]));
}
return resultSb.toString();
}
public static String byteToHexString(byte b) {
int n = b;
if (n < 0) {
n += 256;
}
int d1 = n / 16;
int d2 = n % 16;
return hexDigIts[d1] + hexDigIts[d2];
}
}
另外相同的参数,相同的接口重复的调用,可以直接返回:
{
"code": -1,
"msg": "调用频率太过频繁"
}
虽然有些攻击还是防不到,但是能让你装装逼,让客户看到哇,我这次选的系统接口,很专业。(确实,让我访问接口每访问一次都要加签名什么的,搞得我很难受。)
内容借鉴:(简书作者:Joker_Coding,链接:https://www.jianshu.com/p/ad410836587a)