本质
签发签名
认证签名(使用签名或校验码。这就像一些短信接口的 key 一样 别纠结名字)
accessKey secretKey / appKey appSecret 一样
如果说我们把这个接口提供给开发者,但是我们现在是不是根 本不知道是谁来调用的。假如说我们的服务器只能允许100个 人来调用。假如说有一个攻击者来了,他就刷量了,他想疯狂 的刷我的服务器,那是不是非常的不安全? 另外一方面就是你的服务器的性能肯定会受到损耗,也会影响 正常用户。所以我们肯定要给这个接口,要去做一些保护。比 如说每个用户只能去每秒只能调用十次,这种我们要去做一个 限额。而且后续可能你做大了还要去做收费,对不对?所以我 们肯定要知道是谁来调用的这个接口,而且不能让随便一个没 有权限的人都来找你。 所以,我们怎么知道是谁调用的接口?
即 保证安全性不能随便一个人就能调用
参数1:accessKey:调用的标识(尽量复杂)
参数2:secretKey:密钥
`(类似于用户名、密码,区别:ak\sk无状态)
根据 accessKey 识别调用者的身份
根据 secretKey 识别出签名是否合法
在数据库用户表中新增两个字段 accessKey和secretKey,让用户每次发送请求时,将accessKey 和 secretKey 添加在请求头上一发送
private String accessKey;
private String secretKey;
public danhuaapiClient(String accessKey, String secretKey) {
this.accessKey = accessKey;
this.secretKey = secretKey;
}
private Map getHeaders() {
Map 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();
}
如果仅仅通过参数1,参数2放在请求头的方式进行传递,那么
别人很可能通过重放(二次请求,黑客通过抓包获取到了请求的HTTP报文,然后黑客自己编写了一个类似的HTTP请求,发送给服务器。也就是说服务器处理了两个请求,先处理了正常的HTTP请求,然后又处理了黑客发送的篡改过的HTTP请求)等方式破解
或者拦截你的请求
就如同get请求登录,你把用户名和密码直接拼在地址后面,这肯定是不安全的
重放
f12 -> 网络 -> 选择一个请求 重放XHR
记住!
密码千万不能把放在服务器之间传递
一般是根据密钥生成签名sign
既然以上方法是因为 accessKey 和 secretKey 是明文所导致的,那能不能像,用户登录一样,将secretKey 进行加密,而且采用单向加密(md5,sha256等等)
参数3:用户请求参数
参数4:sign 签名
假如我们有用户参数,我们用密钥和他拼接,用签名算法得到一个 不可解密的值。
用户参数+密钥 => 签名生成算法(MD5,HMac,Sha1) => 不可解 密的值
例子: abc+abcdefg => deskdjhs(得到的值是随机的)
怎么知道签名对不对?
服务器用一模一样的参数和算法去生成签名,只要和用户传的一 致,就标识密钥一致
怎么防止重放?(怎么防止别人拿我们之前发布的请求信息以后再重新再发一次)
public static String genSign(String body,String secretKey) {
Digester md5=new Digester(DigestAlgorithm.SHA256);
String content = body + '*' + secretKey;
return md5.digestHex(content);
}
private Map getHeaders(String body) {
Map 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也会跟着改变
nonce 随机数
timestamp 时间戳