API接口安全设计方案
1、背景
主要从api安全方面考虑。
2、接口安全设计
在代码层面,对接口进行安全设计
一、使用token进行用户身份认证
二、使用sign防止传入参数被篡改
三、用时间戳防止暴力请求
一、使用token进行用户身份认证
用户身份认证的流程图如下:
具体说明如下:
- 用户登录时,客户端请求接口,传入用户名和密文的密码 后台服务对用户身份进行验证。若验证失败,则返回错误结果;若验证通过,则生成一个随机不重复的token,并将其存储在redis中,设置一个过期时间。其中,redis的key为token,value为验证通过后获得的用户信息
- 用户身份校验通过后,后台服务将生成的token返回客户端。客户端请求后续其他接口时,需要带上这个token。后台服务会统一拦截接口请求,进行token有效性校验,并从中获取用户信息,供后续业务逻辑使用
二、使用sign防止传入参数被篡改
为了防止中间人攻击(客户端发来的请求被第三方拦截篡改),引入参数的签名机制。
具体步骤如下:
1、客户端和服务端约定一个加密算法(或MD5摘要也可), 客户端发起请求时,将所有的非空参数按升序拼在一起,通过加密算法形成一个sign,将其放在请求头中传递给后端服务。
2、后端服务统一拦截接口请求,用接收到的非可空参数根据约定好的规则进行加密,和传入的sign值进行比较。若一致则予以放行,不一致则拒绝请求。
由于中间人不知道加密方法,也就不能伪造一个有效的sign。从而防止了中间人对请求参数的篡改。
三、用时间戳防止暴力请求
sign机制可以防止参数被篡改,但无法防dos攻击(第三方使用正确的参数,不停请求服务器,使之无法正常提供服务)。因此,还需要引入时间戳机制。
具体的操作为:客户端在形成sign值时,除了使用所有参数和token外,再加一个发起请求时的时间戳。即
sign值来源 = 所有非空参数升序排序+token+timestamp
而后端则需要根据当前时间和sign值的时间戳进行比较,差值超过一段时间则不予放行。
若要求不高,则客户端和服务端可以仅仅使用精确到秒或分钟的时间戳,据此形成sign值来校验有效性。这样可以使一秒或一分钟内的请求是有效的。
若要求较高,则还需要约定一个解密算法,使后端服务可以从sign值中解析出发起请求的时间戳。
总结后的流程图如下
时间戳机制:用户每次请求都带上当前时间的时间戳timestamp,服务端接收到timestamp后跟当前时间进行比对,如果时间差大于一定时间(比如30秒),则认为该请求失效。时间戳超时机制是防御重复调用和爬取数据的有效手段。
// timeStamp是客户端从Header传过来的值
Long timeStamp = RequestHeaderContext.getInstance().getTimeStamp();
boolean checkTime = checkTime(timeStamp, 30 * 1000);
if (!checkTime) {
return responseErrorAPISecurity(response);
}
// checkTime方法
public static boolean checkTime(Long time, Integer variable){
Long currentTimeMillis = System.currentTimeMillis();
Long addTime = currentTimeMillis + variable;
Long subTime = currentTimeMillis - variable;
if (addTime > time && time > subTime){
return true;
}
return false;
}
签名机制: 将“请求的API参数”+“时间戳”+“盐”进行MD5算法加密,加密后的数据就是本次请求的签名signature,服务端接收到请求后以同样的算法得到签名,并跟当前的签名进行比对,如果不一样,说明参数被更改过,直接返回错误标识。签名机制保证了数据不会被篡改
// 请求的API参数,如果是再body,则MD5;如果是param,则原字符串
StringBuffer urlSign = new StringBuffer();
if ("POST".equals(request.getMethod()) || "PUT".equals(request.getMethod())) {
String bodyStr = RequestReaderUtil.ReadAsChars(request);
String bodySign = "";
if (!StringUtils.isEmpty(bodyStr)){
bodySign = DigestUtils.md5DigestAsHex((bodyStr).getBytes());
}
urlSign = new StringBuffer(bodySign);
} else if ("GET".equals(request.getMethod()) || "DELETE".equals(request.getMethod())) {
String params = request.getQueryString();
if (params == null){
params = "";
}
urlSign = new StringBuffer(params);
}
// “请求的API参数”+“时间戳”+“盐”进行MD5算法加密
String sign = DigestUtils.md5DigestAsHex(urlSign.append(timeStamp).append(salt).toString().getBytes());
// signature是客户端从Header传过来的值
if (signature.equals(sign)) {
return true;
} else {
return false;
}
3、项目拟采用的方案
(1)获取token
这里还是隐藏下了。
(2)接口新增三个字段:token、timestamp、sign
{
"address": "33",
"bussinessType": "22",
"city": "111",
"companyName": "st232ring",
"token": "idfajdjjlkczkvhcklgjkfsj<jjkv",
"timestamp": "20210714164139",
"sign":"fdakfljdkfjdks"
}
(3)签名sign生成规则
规则:sha1(keyvalkeyval+token+timestamp+id)
例如:sha1(address33bussinessType22city111companyNamest232ringtokentimestampid)
这里新增一个id值,与token对应,传输过程中不使用,只用于加密,保证数据即使被截获,因为请求中没有id的传输,更加安全。
(4)几个参数上面已经说过了,简单再说一句。
token身份认证;
timestamp方式防止dos攻击,防止重放,简单说就是一次接口调用,只能用一定时间,比如比对时间,60s内该次调用有效,60秒后失效;
sign签名,通过参数+token+timestamp+id固定位加密,保证参数不会被修改,调用有效;