大家好,欢迎来到IT知识分享网。
本质
accessKey secretKey / appKey appSecret 一样
1. 思考(场景)
2. 为什么选择 API 签名认证
- 保证安全性,不能让任何人都能调用接口
- 适用于无需保存登录态的场景。只认签名,不关注用户登录态。(不关心你是男是女,只要你有 “证” 就行)
3. 实现
3.1 通过 HTTP request header 头传参
参数1:accessKey:调用的标识(尽量复杂)
参数2:secretKey:密钥
`(类似于用户名、密码,区别:ak\sk无状态)
根据 accessKey 识别调用者的身份
根据 secretKey 识别出签名是否合法
在数据库用户表中新增两个字段 accessKey和secretKey,让用户每次发送请求时,将accessKey 和 secretKey 添加在请求头上一发送
3.1.1 demo 实现(使用 hutool 简介 | Hutool)
- 创建 danhuaapiClient 客户端,并加入两个变量、并创建构造函数
private String accessKey; private String secretKey; public danhuaapiClient(String accessKey, String secretKey) { this.accessKey = accessKey; this.secretKey = secretKey; }
- 客户端发送请求时携带请求头
.addHeaders(getHeaders())
private Map<String, String> getHeaders() { Map<String, String> headers = new HashMap<>(); headers.put("accessKey", accessKey); headers.put("secretKey", secretKey); return headers; } public String postNameJson(User user){ String json = JSONUtil.toJsonStr(user); HttpResponse execute = HttpRequest.post("http://localhost:8123/api/name/user") .addHeaders(getHeaders()) .body(json) .execute(); System.out.println("execute :" + execute.getStatus()); String body = execute.body(); System.out.println("body :" + body); return body; }
- 编写测试类
String accessKey = "aikun"; String secretKey = "hd9ehxjue"; danhuaapiClient client = new danhuaapiClient(accessKey, secretKey); String name1 = client.postNameJson(new User("篮球", "ctrl")); System.out.println(name1);
- 服务端校验
这里从请求头里面获取信息,这里的校验只是测试,真实情况应该 是从数据库中去取。
@PostMapping("/user") public String postNameJson(@RequestBody User user, HttpServletRequest request){ String accessKey = request.getHeader("accessKey"); String secretKey = request.getHeader("secretKey"); if (!"aikun".equals(accessKey) || !"hd9ehxjue".equals(secretKey)){ throw new RuntimeException("无权限! 小黑子能不能不要再黑我家哥哥啦~"); } return "post 方法 json 参数 用户的名字: " + user.getUsername(); }
3.1.2 存在的问题
重放
f12 -> 网络 -> 选择一个请求 重放XHR
3.2 优化校验
既然以上方法是因为 accessKey 和 secretKey 是明文所导致的,那能不能像,用户登录一样,将secretKey 进行加密,而且采用单向加密(md5,sha256等等)
3.2.1 参数增加
参数3:用户请求参数
参数4:sign 签名
3.2.2 具体做法
假如我们有用户参数,我们用密钥和他拼接,用签名算法得到一个 不可解密的值。
用户参数+密钥 => 签名生成算法(MD5,HMac,Sha1) => 不可解 密的值
例子: abc+abcdefg => deskdjhs(得到的值是随机的)
怎么知道签名对不对?
服务器用一模一样的参数和算法去生成签名,只要和用户传的一 致,就标识密钥一致
怎么防止重放?(怎么防止别人拿我们之前发布的请求信息以后再重新再发一次)
- 加一个 参数5 nonce 随机数 随机数就我们每次请求的时候发一个随机数。后端记录。后端它只认一 次,后端只接受并认可该随机数一次,一旦被使用过,将不再接受 相同的随机数,解决了重放问题。但后端要保存用过 的随机数,如果接口并发量大,我们需要处理大量的请求,还要定时清理
- 加一个 参数6 timestamp 时间戳,校验时间戳是否过期 发请求时携带一个时间戳,后端会验证其是否在指定的时间范围, 可以控制随机数的过期时间。后端需要同时验证这两个参数,只要时间戳过期或随机数被使用过,后端就会拒绝请求
- 这两个参数还可以联合起来,nonce 不是要定期清理吗,timestamp 过时了就不能用了,那不是就可以根据 timestamp 时间戳的时间进行定期清理 建议redis ,redis 在存储Key-value 本身就可以设置过期时间
3.2.3 实现
- 编写生成 sign 方法的工具类
public static String genSign(String body,String secretKey) { Digester md5=new Digester(DigestAlgorithm.SHA256); String content = body + '*' + secretKey; return md5.digestHex(content); }
- 添加参数
private Map<String, String> getHeaders(String body) { Map<String, String> headers = new HashMap<>(); headers.put("accessKey", accessKey); // headers.put("secretKey", secretKey); headers.put("nonce", RandomUtil.randomNumbers(4)); headers.put("body", body); headers.put("timestamp", String.valueOf(System.currentTimeMillis()/1000)); headers.put("sign", SignUtil.genSign(body, secretKey)); return headers; } public String postNameJson(User user){ String json = JSONUtil.toJsonStr(user); HttpResponse execute = HttpRequest.post("http://localhost:8123/api/name/user") .addHeaders(getHeaders(json)) .body(json) .execute(); System.out.println("execute :" + execute.getStatus()); String body = execute.body(); System.out.println("body :" + body); return body; }
- 参数校验
@PostMapping("/user") public String postNameJson(@RequestBody User user, HttpServletRequest request) { String accessKey = request.getHeader("accessKey"); String nonce = request.getHeader("nonce"); String timestamp = request.getHeader("timestamp"); String sign = request.getHeader("sign"); // 这段代码将从 ISO-8859-1 解码得到的字节流再用 UTF-8 编码,以适应中文字符。 String body = new String(request.getHeader("body").getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8); //校验时间戳 if (System.currentTimeMillis() / 1000 - Long.parseLong(timestamp) > 60 * 5) { throw new RuntimeException("无权限! 小黑子能不能不要再黑我家哥哥啦~"); } // todo 实际上应该去数据库查询 if (!"aikun".equals(accessKey)) { throw new RuntimeException("无权限! 小黑子能不能不要再黑我家哥哥啦~"); } // todo 这里不校验随机数了 if (Long.parseLong(nonce) > 10000) { throw new RuntimeException("无权限! 小黑子能不能不要再黑我家哥哥啦~"); } String serverSign = SignUtil.genSign(body, "hd9ehxjue"); if (!sign.equals(serverSign)) { throw new RuntimeException("无权限! 小黑子能不能不要再黑我家哥哥啦~"); } return "post 方法 json 参数 用户的名字: " + user.getUsername(); }
总结
如何确定访问者的合法性
accessKey secretKey(极其重要,严格保密)
secretKey只有客户端和服务端所知道,但是不参与请求,服务端存在 accessKey -> secretKey 的关系
参数不被修改
sign根据参数,利用算法得出sign,如果你一旦修改一个参数,那么sign也会跟着改变
如何确保请求的唯一性
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/134683.html