原 java 如何保证API接口安全
版权声明:本文为博主原创文章,请尊重他人的劳动成果,转载请附上原文出处链接和本声明。
本文链接:https://www.91mszl.com/zhangwuji/article/details/1347
1)接口如何保证安全。
2)接口需要防止重复提交。
3)对请求链接有一定的时效性,来保证安全。
4)在接口在网络传递过程中被抓包篡改数据。
1)对参数进行RSA加密传递。用公钥进行加密,用私钥进行解密。
2)对接口中的参数进行MD5签名,防止数据被篡改。
3)验证签名,和颁发给调用方的clientId是否有效。
4)采用redisson防止表单重复提交。根据序列号来进行判断。
5)根据用户传递的时间戳和当前系统时间判断改请求是否在有效期内。
一:调用方:
1)对参数按照ASCII进行排序拼接。
2)采用RSA公钥进行加密。
3)对参数进行签名。
4)采用httpClient进行请求。
二:接收方:
1)验证clientId是否正确。
2)验证sign是否正确。
3)判断链接是否在有效期内。
4)判断请求是否重复。
package com.mszl.controller;
import cn.hutool.core.util.IdUtil;
import com.mszl.config.RedissonConfig;
import com.mszl.exception.BusinessException;
import com.mszl.utils.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RLock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.TimeUnit;
/**
* 功能:保证接口安全
* @Author:bobo
* @Date:2021-10-23
* @email:895341748@qq.com
* @website: https://91mszl.com
*/
@Slf4j
@RestController
public class TestApiController {
@Autowired
private RedissonConfig redissonClient;
/**
* 调用方
*/
@PostMapping("/api/encrypt")
public ReturnMsgUtils apiEncrypt() throws Exception {
String clientId="1451913769495171072";
String userId="111";
String productId="222";
String remark="91名师指路";
String timestamp=String.valueOf(System.currentTimeMillis()); // 当前系统时间戳
String serialNo=String.valueOf(IdUtil.getSnowflake(1,1).nextId()); // 保证唯一即可
// 将请求参数的首字母按照按照ASCII进行排列
TreeMap<String, String> parmas=new TreeMap<>();
parmas.put("clientId", clientId);
parmas.put("userId", userId);
parmas.put("productId", productId);
parmas.put("remark", remark);
parmas.put("timestamp", timestamp);
parmas.put("serialNo", serialNo);
String str=ApiUtils.paramsSplice(parmas); // 参数拼接
String sign=Md5.getMD5ofStr(str+BusinessUtils.SECRET_KEY); // 签名
// 加密
String encrypt=URLEncoder.encode(RSAUtils.encrypt(str, BusinessUtils.PUBLIC_KEY), "UTF-8"); // URLEncoder.encode解决字符转义问题
log.info("encrypt : {}", encrypt);
Map<String, String> map=new HashMap<>(16);
map.put("encrypt", encrypt);
map.put("sign", sign);
// 发起请求
String url="http://127.0.0.1:9001/api/decrypt";
String result=HttpClientUtils.sendHttpPost(url, null, map);
log.info("接口返回结果: {}", result);
return ReturnMsgUtils.success(result);
}
/**
* 接收方
*/
@PostMapping("/api/decrypt")
public ReturnMsgUtils apiDecrypt(String encrypt, String sign) throws Exception {
log.info("接收参数 encrypt: {}, sign: {}", encrypt, sign);
// 1 必填参数校验
Map<String, String> map=ApiUtils.judgeParmas(encrypt, sign);
// start 根据自己的业务需求判断参数是否为空:.......
String userId=map.get("userId");
String productId=map.get("productId");
String remark=map.get("remark");
if(StringUtils.isBlank(userId)){
throw new BusinessException(BusinessUtils.BUSINESS_CODE_201, "userId不能为空");
}
if(StringUtils.isBlank(productId)){
throw new BusinessException(BusinessUtils.BUSINESS_CODE_201, "productId不能为空");
}
// end 根据自己的业务需求判断参数是否为空:.......
// 2 验证clientId是否存在
String clientId=map.get("clientId");
if(!clientId.equals(BusinessUtils.CLIENT_ID)){
throw new BusinessException(BusinessUtils.BUSINESS_CODE_201, "clientId不能存在");
}
// 3 验证sign是否有效
String serialNo=map.get("serialNo");
String timestamp=map.get("timestamp");
TreeMap<String, String> parmas=new TreeMap<>();
parmas.put("clientId", BusinessUtils.CLIENT_ID);
parmas.put("userId", userId);
parmas.put("productId", productId);
parmas.put("remark", remark);
parmas.put("timestamp", timestamp);
parmas.put("serialNo", serialNo);
String str=ApiUtils.paramsSplice(parmas);
String receiveSign=Md5.getMD5ofStr(str+BusinessUtils.SECRET_KEY);
log.info("receiveSign : {}", receiveSign);
if(!sign.equals(receiveSign)){
throw new BusinessException(BusinessUtils.BUSINESS_CODE_201, "sign错误");
}
// 4 判断是否超过时间5分钟
long difference=System.currentTimeMillis() - Long.parseLong(timestamp);
if(Math.abs(difference) > 1000 * 60 * 5){ // 大于5分钟
throw new BusinessException(BusinessUtils.BUSINESS_CODE_201, "链接已失效");
}
// 5 判断请求是否重复:
String key=clientId+"_"+serialNo;
RLock lock = redissonClient.redissonClient().getLock(key);
boolean res = lock.tryLock(0, 10, TimeUnit.SECONDS); // 尝试加锁,最多等待0秒,上锁以后10秒自动解锁
if(!res){
throw new BusinessException(BusinessUtils.BUSINESS_CODE_201, "请勿重复提交");
}
// start 处理自己的业务逻辑。。。。。。。。。。。。。
// end 处理自己的业务逻辑。。。。。。。。。。。。。
return ReturnMsgUtils.success();
}
}
2.1 测试接口重复提交
我们将调用方的serialNo改成123456,是否重复提交就是根据序列号字段来进行判断的
重启idea,然后采用postman进行请求,第一次请求正常,第二次请求时提示我们不要重复提交。
2.2 测试接口是否在5分钟以后失效。
时间戳:1635000326793,对应的年月日:2021-10-23 22:45:26,我们将调用方的时间戳改成这个。或者你将5分钟改成1分钟都可以,如果你不想改,也可以喝一杯咖啡或茶了在回来。
重启idea,采用postman请求
到此关键的测试情况都演示了,还有验证必填项非空,clientId等就让大家自己去验证。
完整项目下载,请点击文章最后的下载源码 按钮进行下载,给作者晚餐加一个鸡腿。
参考资料:
https://mp.weixin.qq.com/s/zjKWqW-Wpbi2GnhSs5fwew
https://segmentfault.com/a/1190000039859234
2021-10-21 09:26:57 阅读(605)
名师出品,必属精品 https://www.91mszl.com
博主信息