文章目录

  • 一、定义
  • 二、签名
  • 三、相应工具类
  • 四、测试get请求,参数写url上
  • 五、post请求,参数放入body中
  • 六、使用过滤器配置接口防篡改
    • 一、相关工具类
    • 二、测试

一、定义

在客户端与服务端请求交互的过程中,请求的数据容易被拦截并篡改,比如在支付场景中,请求支付金额为 10 元,被拦截后篡改为 100 元,由于没有防篡改校验,导致多支付了金钱,造成了用户损失。因此我们在接口设计时必须考虑防篡改校验,加签、验签就是用来解决这个问题的。划重点,敲黑板:加签、验签是用来解决防篡改问题的。

签名主要包含摘要和非对称加密两部分内容,首先对需要签名的数据进行摘要计算得到摘要值,然后通过签名者的私钥对摘要值进行非对称加密即可得到签名结果。

验签主要包含摘要、非对称解密、摘要比对三部分内容,首页对接收到的数据进行摘要计算得到验签方摘要值,然后通过签名者的公钥对摘要值进行非对称解密得到签名方摘要值,将签名方摘要值与验签方摘要值进行比对,如果相等则验签成功,否则验签失败。

二、签名

1、参数排序
将需要签名的内容根据参数名称进行排序,排序规则按照第一个字符的ASCII码值递增排序(字母升序排序),如果遇到相同字符则按照第二个字符的ASCII码递增排序,以此类推。将参数内容进行排序,可以保证签名、验签双方参数内容的一致性。

为什么会产生不一致?

签名方以 Json 格式将参数内容发送给验签方,验签方需要将 Json 格式的参数内容反序列化为对象,由于验签方可能使用不同的编程语言,不同的 Json 框架,所以会导致双方的参数顺序不一致。

2、参数拼接
将排序后的参数与其对应值,组合成“参数=参数值”的格式,并且把这些参数用&字符连接起来,此时生成的字符串为待摘要字符串。

3、摘要计算
通过摘要算法求待摘要字符串的摘要值,常用的摘要算法如MD5、SHA、HMAC等。

4、非对称加密
使用非非对称加密算法,利用客户端的私钥对摘要值进行加密,生成内容我们称之为签名。

5、发送请求
将参数内容、字符编码、签名方法(非对称加密算法)、签名发送给验签方。

验签
验签方收到请求后进行验签。

三、相应工具类

1、SHA256Util加密算法工具类:

public class SHA256Util {/*** @param str 加密前的报文* @Author: Mr.ZJW* @Description: 用java原生的摘要实现SHA256加密* @Date: 2022/5/5 14:14**/public static String getSHA256String(String str) {String encodeStr = "";try {MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");messageDigest.update(str.getBytes("UTF-8"));encodeStr = byte2Hex(messageDigest.digest());} catch (NoSuchAlgorithmException e) {e.printStackTrace();} catch (UnsupportedEncodingException e) {e.printStackTrace();}return encodeStr;}/*** @param [bytes]* @Author: Mr.ZJW* @Description: byte[]转为16进制* @Date: 2022/5/5 14:15**/private static String byte2Hex(byte[] bytes) {StringBuffer stringBuffer = new StringBuffer();for (int i = 0; i < bytes.length; i++) {String temp = Integer.toHexString(bytes[i] & 0xFF);if (temp.length() == 1) {stringBuffer.append("0");}stringBuffer.append(temp);}return stringBuffer.toString();}
}

2、生产签名工具类

/*** @Author: Mr.ZJW* @Date: 2022-05-05 11:36* @Description: 生产签名工具类*/
public class SignUtil {private static String secret = "e10adc3949ba59abbe56e057f20f883f";/*** @param [map]* @Author: Mr.ZJW* @Description: 根据Map生成签名* @Date: 2022/5/5 11:38**/public static String generatorSign(Map<String, Object> map) {map.remove("sign");//排序Map<String, Object> stringObjectMap = MapSortUtil.sortMapByKey(map);//转格式Set<Map.Entry<String, Object>> entries = stringObjectMap.entrySet();//存放StringBuilderStringBuilder sb = new StringBuilder();//遍历for (Map.Entry<String, Object> entry : entries) {sb.append(entry.getKey() + ":" + entry.getValue()).append("&");}//组装secretsb.append("secret").append(secret);//生产签名return SHA256Util.getSHA256String(sb.toString());}/*** @Author: Mr.ZJW* @Description: 校验签名* @param [map]* @Date: 2022/5/6 11:11**/public static Boolean checkSign(Map<String,Object> map){String sign = (String) map.get("sign");map.remove("sign");//生产SignString signGenera = generatorSign(map);//校验Signif (signGenera.equals(sign)){return true;}return false;}
}

3、Map排序工具类

/*** @Author: Mr.ZJW* @Date: 2022-05-05 10:59* @Description: Map排序工具类*/
public class MapSortUtil {/*** @Author: Mr.ZJW* @Description: Map排序工具类* @Date: 2022/5/5 11:01**/public static Map<String, Object> sortMapByKey(Map<String, Object> map) {//判断是否为空if (ObjectUtils.isEmpty(map)) {throw new RuntimeException("输入参数为空");}//排序Map<String, Object> sortMap = new TreeMap<>(new MyMapComparator());sortMap.putAll(map);return sortMap;}static class MyMapComparator implements Comparator<String> {@Overridepublic int compare(String o1, String o2) {return o1.compareTo(o2);}}
}

四、测试get请求,参数写url上

1、测试内容,这里简单测试两个appId以及name

    public static void main(String[] args) {HashMap<String, Object> map = new HashMap<>();map.put("appId", 1);map.put("name", "jowell");String s = generatorSign(map);System.out.println("s = " + s);}


2、controller代码

    /*** @Author: Mr.ZJW* @Description: get请求,参数写url上* @param [sign, request]* @Date: 2022/5/5 17:22**/@GetMapping("/getTest")public String getTest(String sign,HttpServletRequest request){HashMap<String, Object> map = new HashMap<>();// 获取get中的参数Enumeration<String> parameterNames = request.getParameterNames();while (parameterNames.hasMoreElements()){//获取nameString parametename = parameterNames.nextElement();// 获取值String parameterValue = request.getParameter(parametename);map.put(parametename,parameterValue);}//排序Map<String, Object> map1 = MapSortUtil.sortMapByKey(map);//生产签名String sign1 = SignUtil.generatorSign(map1);//判断签名if (sign.equals(sign1)){return "success";}return "error";}

3、启动项目测试效果
如下图测试成功:

我们把appId内容改为2,可以可以看到请求接口失败,无论是改了内容还是改了签名,都请求不成功,这样就防止了第三方而已者篡改接口内容

五、post请求,参数放入body中

测试内容同上

1、把请求参数封装为实体类

@Data
public class SignDTO {private String appId;private String name;private String sign;
}

2、controller

    /*** @Author: Mr.ZJW* @Description: post请求,参数放入body中* @param [signDTO]* @Date: 2022/5/5 17:09**/@PostMapping("/postTest")public String postTest(@RequestBody SignDTO signDTO) {//JSON转对象JSONObject jsonObject = JSONUtil.parseObj(signDTO);//转MapMap<String, Object> map = Convert.toMap(String.class, Object.class, jsonObject);//排序Map<String, Object> map1 = MapSortUtil.sortMapByKey(map);System.out.println("map1 = " + map1);//生成String sign = SignUtil.generatorSign(map1);//判断签名if (sign.equals(signDTO.getSign())){return "校验通过";}return "校验失败";}

3、启动项目测试

但通过上面代码可以看到,代码非常冗余,每次写一次controller都得写签名校验,下面把签名验证改为统一过滤器。

六、使用过滤器配置接口防篡改

一、相关工具类

1、Sign过滤器类

@Slf4j
@Component
public class SignAuthFilter implements Filter {@Overridepublic void init(FilterConfig filterConfig) throws ServletException {}@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {//ServletRequest转HttpServletRequestHttpServletRequest req = (HttpServletRequest) servletRequest;//获取请求路径final String uri = req.getRequestURI().startsWith("/") ? req.getRequestURI().substring(1) : req.getRequestURI();//对以下请求放行if(uri.contains("user/getCaptcha")){filterChain.doFilter(servletRequest, servletResponse);return;}//转HttpServletRequest以及HttpServletResponseHttpServletRequest request = new BodyReaderHttpServletRequestWrapper((HttpServletRequest) servletRequest);HttpServletResponse response = (HttpServletResponse) servletResponse;//获取请求参数工具类,包括是get或postSortedMap<String, Object> allParams = HttpParamUtil.getAllParams(request);log.info("所有请求参数:{}", allParams);//校验签名Boolean flag = SignUtil.checkSign(allParams);if (flag){filterChain.doFilter(request, response);}else {response.setCharacterEncoding("utf-8");response.setContentType("application/json;charset=utf-8");PrintWriter writer = response.getWriter();JSONObject jsonObject = new JSONObject();jsonObject.put("msg","签名不正确");jsonObject.put("code",-1);writer.println(jsonObject);}}@Overridepublic void destroy() {}}

2、保存过滤器里面的流工具类

/*** @Author: Mr.ZJW* @Description: 保存过滤器里面的流* @Date: 2022/5/6 15:03
**/
public class BodyReaderHttpServletRequestWrapper extends HttpServletRequestWrapper {private final byte[] body;public BodyReaderHttpServletRequestWrapper(HttpServletRequest request) {super(request);String sessionStream = getBodyString(request);body = sessionStream.getBytes(Charset.forName("UTF-8"));}/*** 获取请求Body** @param request* @return*/public String getBodyString(final ServletRequest request) {StringBuilder sb = new StringBuilder();try (InputStream inputStream = cloneInputStream(request.getInputStream());BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, Charset.forName("UTF-8")))) {String line;while ((line = reader.readLine()) != null) {sb.append(line);}} catch (IOException e) {e.printStackTrace();}return sb.toString();}/*** Description: 复制输入流</br>*/public InputStream cloneInputStream(ServletInputStream inputStream) {ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();byte[] buffer = new byte[1024];int len;try {while ((len = inputStream.read(buffer)) > -1) {byteArrayOutputStream.write(buffer, 0, len);}byteArrayOutputStream.flush();} catch (IOException e) {e.printStackTrace();}return new ByteArrayInputStream(byteArrayOutputStream.toByteArray());}@Overridepublic BufferedReader getReader() {return new BufferedReader(new InputStreamReader(getInputStream()));}@Overridepublic ServletInputStream getInputStream() {final ByteArrayInputStream bais = new ByteArrayInputStream(body);return new ServletInputStream() {@Overridepublic int read() {return bais.read();}@Overridepublic boolean isFinished() {return false;}@Overridepublic boolean isReady() {return false;}@Overridepublic void setReadListener(ReadListener readListener) {}};}
}

3、获取请求参数工具类,不管是get或post

/*** @Author: Mr.ZJW* @Date: 2022-05-06 9:30* @Description: 获取请求参数工具类,不管是get或post*/
public class HttpParamUtil {/*** @param [request]* @Author: Mr.ZJW* @Description: 获取请求中的所以参数,包括get或post* @Date: 2022/5/6 10:25**/public static SortedMap<String, Object> getAllParams(HttpServletRequest request) throws IOException {//总的参数mapSortedMap<String, Object> allMap = new TreeMap<>();//获取URL上的参数if (StringUtils.isNotEmpty(request.getQueryString())) {Map<String, Object> urlParams = getUrlParams(request);//遍历URL上的参数for (Map.Entry entry : urlParams.entrySet()) {allMap.put((String) entry.getKey(), entry.getValue());}}//获取Body上的参数Map<String, String> bodyParams = getBodyParams(request);if (ObjectUtils.isNotEmpty(bodyParams)) {//遍历Body上的参数for (Map.Entry entry : bodyParams.entrySet()) {allMap.put((String) entry.getKey(), entry.getValue());}}return allMap;}/*** @param [request]* @Author: Mr.ZJW* @Description: 获取Body上的参数* @Date: 2022/5/6 9:52**/private static Map<String, String> getBodyParams(HttpServletRequest request) throws IOException {//读取Body中的参数BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(request.getInputStream()));StringBuilder sb = new StringBuilder();String s = "";while (null != (s = bufferedReader.readLine())) {sb.append(s);}//转Mapreturn JSONObject.parseObject(sb.toString(), Map.class);}/*** @param [request]* @Author: Mr.ZJW* @Description: 获取URL上的参数* @Date: 2022/5/6 9:52**/private static Map<String, Object> getUrlParams(HttpServletRequest request) {String queryParam = "";try {//查询URL上的请求参数queryParam = URLDecoder.decode(request.getQueryString(), "utf-8");} catch (UnsupportedEncodingException e) {e.printStackTrace();}HashMap<String, Object> result = new HashMap<>();//分隔:如//hello?a=1&b=2  分隔&String[] split = queryParam.split("&");//遍历for (String s : split) {int i = s.indexOf("=");result.put(s.substring(0, i), s.substring(i + 1));}return result;}}

二、测试

1、controller代码,如下可以看到代码清晰了很多,只关注业务代码即可

    /*** @param [sign, request]* @Author: Mr.ZJW* @Description: get请求,参数写url上* @Date: 2022/5/5 17:22**/@GetMapping("/getTest")public String getTest(String sign, HttpServletRequest request) {System.out.println("进入get请求,参数写url上方法");return "getTest";}/*** @param [signDTO]* @Author: Mr.ZJW* @Description: post请求,参数放入body中* @Date: 2022/5/5 17:09**/@PostMapping("/postTest")public String postTest(@RequestBody SignDTO signDTO) {System.out.println("进入post请求,参数放入body中方法");return "postTest";}

2、测试



如下可以看到,只要我修改了内容就验证不通过,就判定接口是被第三方恶意篡改过的

get请求测试同上,自行测试

Java实现接口防篡改相关推荐

  1. java参数防篡改,Java程序防篡改器设计方案

    一.引言 "安全性"一直是Java语言所强调的重点之一.基于安全性的考虑,Java程序所受到的限制比一般由C语言或汇编语言编写的原生程序(NativeProgram)严格了许多.举 ...

  2. 【后端-接口设计】java应用接口授权鉴权与URL防篡改详细设计

    目录 一.背景 二.设计方案 三.详细设计 1. 新增配置项 2. 新增接口授权管理页面 3. 接口授权过滤 4. URL防篡改支持 5. 对系统SDK请求授权支持 6. 数据库设计 7. 接口设计 ...

  3. php重放,Api 接口安全-防篡改,防重放理解总结

    防篡改 为什么要防篡改 http 是一种无状态的协议, 服务端并不知道客户端发送的请求是否合法, 也并不知道请求中的参数是否正确 举个栗子, 现在有个充值的接口, 调用给用户对应的余额 http:// ...

  4. java接口防刷_API 接口防刷

    API 接口防刷 顾名思义,想让某个接口某个人在某段时间内只能请求N次. 在项目中比较常见的问题也有,那就是连点按钮导致请求多次,以前在web端有表单重复提交,可以通过token 来解决. 除了上面的 ...

  5. gateway+vue实现防接口重放、防篡改

    这里写目录标题 引言 解决思路 核心代码 前端代码 增加请求头 工具类 后端代码 全局过滤器 配置类 工具类 请求封装类 mdb加密工具类 校验类 防重放验证类 防篡改验证 主要花精力的地方 需要知道 ...

  6. 如何处理java接口防重提交

    1.什么是接口防重? 在一定的时间内多次请求同一接口,同一参数.由于请求是 健康请求 ,会执行 正常的业务逻辑 ,从而产生大量的废数据. 2.问题的产生及引发的问题 举一个最简单的例子:日常开发中cr ...

  7. Java接口防刷策略(自定义注解实现)

    前言 本文一定要看完,前部分为逻辑说明及简单实现,文章最后有最终版解决方案(基于lua脚本),因为前部分是防君子不防小人,无法抵挡for循环调用. 目的 短信发送及短信验证码校验接口防刷 一方面防止用 ...

  8. 【Java秒杀方案】11.功能开发-【商品秒杀及优化】防止超卖 接口优化(redis预减库存,内存标记减少redis访问,RabbitMQ异步下单) 安全优化(隐藏秒杀接口,验证码,接口防刷)

    商品秒杀核心功能及优化 1. 正常秒杀流程 在商品详情页面等待秒杀倒计时–http://localhost:8080/goodsDetail.htm?goodsId=2 倒计时为0,开始秒杀,点[秒杀 ...

  9. java 防篡改_用JAVA二十分钟撸一个简易图片防篡改

    看到有个毕设是搞图片防篡改的,就自己撸了一个简易图片防止篡改. 原理 将图片字节生成字符串使用摘要算法加密,将加密生成的字节写到图片最后.验证时,首先读取末尾的加密字节,读取完成以后删除,再通过摘要算 ...

最新文章

  1. 在ArcEngine下实现图层属性过滤的两种方法
  2. 创建Cocos2d-x 3.x项目以及项目结构简要说明
  3. 验算神经网络谐振子模型的第二组数据
  4. C/C++ 代码转换规范化的脚本
  5. 编程生涯 21 载,那些我踩过的坑
  6. docker容器内开启22 ssh_在docker容器中开启ssh服务 (未成功有时间再验证)
  7. Kafka/Metaq设计思想学习笔记 转
  8. Wattagio for Mac(电池管理) 免激活版
  9. 使用PL/SQL Developer 远程连接Oracle数据库出现 “无监听程序“错误 的解决办法
  10. 绿点 | 区块链介入下的绿色经济 x 妳格局LadyVision x WinMap+!
  11. GBase 8c 备份控制函数(三)
  12. Mac电脑清空搜狗输入法联想记忆词库
  13. Learning to Rank基于pairwise的算法(三)—— RankNet、FRank、LambdaRank
  14. Python 使用mutagen批量修改MP3标签信息
  15. 普通话测试软件测分准吗,普通话学习测试类 | 普通话测试APP,能做到测试、学习提高两不误吗?...
  16. 雅虎邮箱停用对网民的影响
  17. Gartner研究副总裁关于商务智能(BI)的三大趋势分析的讲话(录音翻译)
  18. php查询mysql充值_PHP + MYSQL 实现 用户注册/登录/充值 功能
  19. SpreadJS企业表格技术实践三:财务预算一体化系统
  20. RHCA回忆录---DO447介绍

热门文章

  1. 工程伦理--8.1 利益冲突问题
  2. 【精】iOS6 及其以上版本自动旋转、手动强制旋转方案及布局适配
  3. html5边框边距,html样式(边框)
  4. uniapp之H5网站添加图标favicon浏览器标签栏小图标
  5. 职场霸凌、潜规则,已经这么猖獗了吗。。。
  6. 在cmd或者powerShell窗口运行java的jar文件
  7. Android各大手机系统打开权限管理页面
  8. java计算机毕业设计HTML5果蔬经营平台MyBatis+系统+LW文档+源码+调试部署
  9. 计算机有音乐升降调功能吗,音乐中的降调和升调是什么意思?
  10. python调用微信发送消息过于频繁_python 调用微信 发消息