文章目录

  • 一、定义
  • 二、签名
  • 三、相应工具类
  • 四、测试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. hue sqoop mysql_HUE中Oozie执行Sqoop
  2. 【Linux入门到精通系列讲解】系统调用和库函数路径
  3. MySql默认编码所造成的乱码麻烦1.222
  4. golang int64转string_(一)Golang从入门到原地起飞
  5. php注入类,简单实用的PHP防注入类实例_PHP
  6. 2018年4月10日--python解决乱码和作业
  7. C++ 訪问控制权限图解
  8. 在移动端H5开发中(关于安卓端position:fixed和position:absolute;和虚拟键盘冲突的问题,以及解决方案)...
  9. 习题4-8 高空坠球(20 分)
  10. Apache Flink 官方文档--流(DataStream API)-旁路输出
  11. qq空间显示手机型号android,任意修改QQ空间发表说说显示的手机型号
  12. 5款自动爬取数据的神器!
  13. 正确使用计算机键盘的方法是,电脑键盘指法练习的方法
  14. 检查压缩包是否损坏_【安全知识】运动安全带检查PPE(个人防护装备)检查程序与表格...
  15. 【电商业务】外行为何难区别 商品属性与商品规格
  16. 连续8年 远见领跑|山石网科入选Gartner 2021网络防火墙魔力象限
  17. 除法的向上取整和向下取整
  18. javascript实现QR code二维码扫描
  19. Tensorflow选择CPU或GPU运行
  20. GBase 8a支持国产CPU,ARM CPU,华为泰山的鲲鹏(Kunpeng),曙光的海光(Hygon),申威(SW)

热门文章

  1. AndEngine 《Android游戏开发实践指南》之“吸血鬼游戏”实例学习(一)
  2. FIR滤波器文献笔记
  3. 没有之一,最美的接口管理神器
  4. 日本宣布建8个量子研发基地,狂追中、美两国,意味着什么?
  5. C++程序设计案例实训教程第2章(qq讨论群112133686)
  6. 创新指南|企业创新可以3步做对深科技战略
  7. lisp 代码计算月供
  8. Window同一芯片,禁用串口序列号,固定端口号9091和9008
  9. 同事多线程使用不当导致OOM,被我怼了一顿
  10. PHP游戏服务器之GlobalData组件的运用