API 签名认证
我们现在的接口是暴露在外的,不能随便让其他人不停刷接口,导致服务崩溃。因此,我们需要保证让用户无法无限调用接口。保证安全性。但是我们的接口是开放的,用户未登录也可以去调用接口,因此无法直接通过 Cookie 下的 Session 去取出用户来做限制。因此要用别的方法。
接口认证介绍:
参考:Java API接口签名认证_java设计接口需要签名认证-CSDN博客
什么是接口签名认证
比如,申请了微信公众号或者小程序,公众号的基本信息中就会包含AppId和AppSecret两个数据。这两个数据需要我们用户进行保存,尤其是AppSecret更不能暴露在外,以保证安全。当我们需要请求微信进行授权登录的时候,我们需要根据微信的规则拼接请求链接,其中请求链接中会包含AppId,AppSecret等的一些信息,通过按规则拼接好的链接则可以成功请求微信,否则会请求失败。
而我们要做的就是根据微信的这个原理,实现自己程序的API接口签名验证。
本质:
- 签发签名
- 使用签名(校验签名)
为什么需要签名认证?
- 保证安全性,不能随便一个人调用
详细地说就是:
提供了一个能够让用户不用登录账号就能调用接口的方法,用户只需要提供自己的 accessKey 和 secretKey 即可自由地在自己的代码中调用平台的接口。
怎么实现API签名认证?
accessKey:调用的标识,可以发送给后端。
SecretKey:密钥,注意该参数是不传递给后端的,它需要被加密后才会发送。
我们要求用户不管是否登录,每次调用接口都要携带这两个数据。 ak,sk (即 accessKey ,SecretKey )是无状态的,和用户名密码的有状态是区别开的。
可以自己写代码生成 ak 和 sk 比如 MD5 加密
千万不能把密钥在服务器之间传递,有可能被拦截
常见加密方式: 对称加密、非对称加密、MD5 签名(不可解密)
用户参数 + 密钥 => 签名算法 => 不可解密的值
客户端用上述的方式生成了不可解密的值,发送给后端。
后端利用一模一样的算法和参数去生成签名,只要和用户传的值一致。就表示一致。
请求重放(重放攻击)
用户向网站发送的一个请求,可以被拦截之后,被修改一些参数之后再次发送。
参考文章:面试官:啥是请求重放呀? - why技术 - 博客园 (cnblogs.com)
怎么防重放?
加 nonce 随机数,后端只会认识一次这个随机数,一旦使用过,就无法再被使用。
问题:服务端要保存用过的随机数,如果对于高并发量,高访问量的请求,需要存储大量的保存过的随机数。
加 timestamp 时间戳,每个请求都要发送一个时间戳,后端会限制只有在一定时间下的请求才能通过,校验时间戳是否过期。对于已经过期的随机数,可以将其清除,缓解存储空间。
综上,一个标准的签名认证算法,需要至少 5 个参数:
- accessKey
- SecretKey
- 用户请求参数
- sign (标志)
- 随机数 nonce
- 时间戳 timestamp
注意密码一定不要在服务器之间传输!!!
API 签名认证是一个很灵活的设计,具体要有哪些参数、参数名如何一定要根据场景来。(比如 userId 、appId、version、固定值等)
一个简单的 API 签名认证实现:
- getHeaderMap 方法,将需要签名的参数放入到一个 HashMap 中。
public Map<String,String> getHeaderMap(String body){
HashMap<String, String> hashMap = new HashMap<>();
hashMap.put("accessKey",accessKey);
hashMap.put("body",body);
hashMap.put("nonce", RandomUtil.randomNumbers(4));
hashMap.put("timestamp",String.valueOf(System.currentTimeMillis()/1000));
hashMap.put("sign", signUtils.genSign(body,secretKey));
return hashMap;
}
- 后端内部向接口发送请求。同时在请求头中添加调用了 getHeaderMap 方法所生成的 json。
// 使用POST方法向服务器发送User对象,并获取服务器返回的结果
public String getUserNameByPost(User user) {
// 将User对象转换为JSON字符串
String json = JSONUtil.toJsonStr(user);
// 使用HttpRequest工具发起POST请求,并获取服务器的响应
HttpResponse httpResponse = HttpRequest.post("http://localhost:8090/api/name/user/")
.body(json)
//将整个 json 作为请求头,去混入到 API 签名认证中。
.addHeaders(getHeaderMap(json))
.execute(); // 执行请求
// 打印服务器返回的状态码
System.out.println(httpResponse.getStatus());
// 获取服务器返回的结果
String result = httpResponse.body();
// 打印服务器返回的结果
System.out.println(result);
// 返回服务器返回的结果
return result;
}
在请求服务端需要预先将请求头中的数据取出,校验请求头数据,若请求头的校验通过,就使用相同的加密算法得出相应的密钥,如果密钥匹配,则执行相应的业务。