【JAVA】实现API 接口参数签名

使用sa-token+SpringBoot+拦截器实现API 接口参数签名

在涉及跨系统接口调用时,我们容易碰到以下安全问题:
1.请求身份被伪造。
2.请求参数被篡改。
3.请求被抓包,然后重放攻击。

1.引入 sa-token
sa-token官方文档:https://sa-token.cc/doc.html#/

<dependency><groupId>cn.dev33</groupId><artifactId>sa-token-spring-boot-starter</artifactId><version>1.35.0.RC</version>
</dependency>

2.配置密钥

请求发起端和接收端需要配置一个相同的秘钥,在 application.yml 中配置

# 开发接口密钥配置
sa-token:sign:# API 接口签名秘钥secret-key: 8ba6126f-3921-4eca-8f1b-451aa38a563b

3.重写HttpServletRequestWrapper类

方便获取请求头的参数,包括@RequestBody注解接受的参数

package com.xhs.interceptor;import org.apache.commons.io.IOUtils;import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;/*** @desc: 保存请求体参数的内容* @projectName: java-tools-parent* @author: xhs* @date: 2023-8-27 027 19:06* @version: JDK 1.8*/
public class RequestWrapper extends HttpServletRequestWrapper {/*** 保存请求体参数*/private final String body;/*** 保存其他类型的参数*/private final Map<String, String[]> parameterMap;/*** 获取参数** @param request request* @throws IOException IOException*/public RequestWrapper(HttpServletRequest request) throws IOException {super(request);// 获取请求体参数body = IOUtils.toString(request.getInputStream(), StandardCharsets.UTF_8);// 获取其他类型的参数parameterMap = new HashMap<>(request.getParameterMap());}@Overridepublic BufferedReader getReader() throws IOException {return new BufferedReader(new InputStreamReader(getInputStream()));}@Overridepublic ServletInputStream getInputStream() throws IOException {final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes());return new ServletInputStream() {@Overridepublic boolean isFinished() {return false;}@Overridepublic boolean isReady() {return false;}@Overridepublic void setReadListener(ReadListener readListener) {}@Overridepublic int read() {return byteArrayInputStream.read();}};}@Overridepublic String getParameter(String name) {String[] values = parameterMap.get(name);if (values != null && values.length > 0) {return values[0];}return null;}@Overridepublic Map<String, String[]> getParameterMap() {return parameterMap;}@Overridepublic Enumeration<String> getParameterNames() {return Collections.enumeration(parameterMap.keySet());}@Overridepublic String[] getParameterValues(String name) {return parameterMap.get(name);}/*** 获取请求体参数** @return String*/public String getBody() {return body;}
}

4.创建签名校验的拦截器 SignInterceptor

校验请求的参数是否有效

package com.xhs.interceptor;import cn.dev33.satoken.sign.SaSignUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpMethod;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;/*** @desc: 签名校验的拦截器* @projectName: java-tools-parent* @author: xhs* @date: 2023-8-27 027 17:56* @version: JDK 1.8*/
@Slf4j
public class SignInterceptor implements HandlerInterceptor {/*** 创建一个签名校验的拦截器*/public SignInterceptor() {}/*** 每次请求之前触发的方法** @param request  request* @param response response* @param handler  handler* @return boolean*/@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 如果是OPTIONS请求,让其响应一个 200状态码,说明可以正常访问if (HttpMethod.OPTIONS.toString().equals(request.getMethod())) {response.setStatus(HttpServletResponse.SC_OK);// 放行OPTIONS请求return true;}// 保存传递过来的参数Map<String, String> map = new HashMap<>(16);// 在拦截器中获取处理方法的参数,并检查是否带有 @RequestBody 注解HandlerMethod handlerMethod = (HandlerMethod) handler;MethodParameter[] methodParameters = handlerMethod.getMethodParameters();for (MethodParameter parameter : methodParameters) {if (parameter.hasParameterAnnotation(RequestBody.class)) {// 参数带有 @RequestBody 注解// 获取请求体参数RequestWrapper requestWrapper = new RequestWrapper(request);String requestBody = requestWrapper.getBody();if (StringUtils.hasLength(requestBody)) {JSONObject jsonObject = JSONObject.parseObject(requestBody);map = JSON.parseObject(jsonObject.toJSONString(), HashMap.class);log.info("请求体参数map:{}", map);}} else {// 参数不带 @RequestBody 注解// 非请求体参数Enumeration<String> parameterNames = request.getParameterNames();// 遍历参数名,并获取对应的参数值while (parameterNames.hasMoreElements()) {String paramName = parameterNames.nextElement();String paramValue = request.getParameter(paramName);// 处理动态参数,如打印参数名和参数值map.put(paramName, paramValue);}log.info("非请求体参数map:{}", map);}}// 1、校验请求中的签名SaSignUtil.checkParamMap(map);return true;}
}

5.使用拦截器

配置那些接口需要校验参数签名

package com.xhs.filter;import com.xhs.interceptor.SignInterceptor;
import org.jetbrains.annotations.NotNull;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;/*** @desc: 检查签名过滤器* @projectName: java-tools-parent* @author: xhs* @date: 2023-8-27 027 15:17* @version: JDK 1.8*/
@Configuration
public class SignFilter implements WebMvcConfigurer {/*** 注册拦截器** @param registry* @return void*/@Overridepublic void addInterceptors(@NotNull InterceptorRegistry registry) {// 校验规则为registry.addInterceptor(new SignInterceptor())//需要校验的接口.addPathPatterns("/tools/getUser","/tools/getName")// 不需要校验的接口.excludePathPatterns();}
}

6.创建HttpServletRequestFilter过滤器

解决@RequestBody注解接受的参数,校验完签名后报:
org.springframework.http.converter.HttpMessageNotReadableException: Required request body is missing:
异常

package com.xhs.filter;import com.xhs.interceptor.RequestWrapper;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.support.StandardServletMultipartResolver;import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;/*** @desc: 过滤器* 解决:在拦截器中获取body后,接口报错:Required request body is missing* @projectName: java-tools-parent* @author: xhs* @date: 2023-8-27 027 20:07* @version: JDK 1.8*/
@Component
@WebFilter(filterName = "HttpServletRequestFilter", urlPatterns = "/")
public class HttpServletRequestFilter implements Filter {@Overridepublic void init(FilterConfig filterConfig) throws ServletException {Filter.super.init(filterConfig);}@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {HttpServletRequest request = (HttpServletRequest) servletRequest;String contentType = request.getContentType();String method = "multipart/form-data";if (contentType != null && contentType.contains(method)) {// 将转化后的 request 放入过滤链中request = new StandardServletMultipartResolver().resolveMultipart(request);}request = new RequestWrapper((HttpServletRequest) servletRequest);//获取请求中的流如何,将取出来的字符串,再次转换成流,然后把它放入到新request对象中// 在chain.doFiler方法中传递新的request对象if (request == null) {filterChain.doFilter(servletRequest, servletResponse);} else {filterChain.doFilter(request, servletResponse);}}
}

7.创建生成签名的方法
7.1 controller层代码

package com.xhs.controller;import com.xhs.message.ReturnResult;
import com.xhs.service.SignService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;import javax.annotation.Resource;
import java.util.Map;/*** @desc: 生成签名* @projectName: java-tools-parent* @author: xhs* @date: 2023-8-27 027 16:03* @version: JDK 1.8*/
@Slf4j
@RestController
public class SignController {@Resourceprivate SignService signService;/*** 生成签名 参数拼接到url后面** @param paramsMap 参数* @return ReturnResult<Object>*/@PostMapping("/signGet")public ReturnResult<Object> signGet(@RequestBody Map<String, Object> paramsMap) {return signService.signGet(paramsMap);}/*** 生成签名 JSON格式的参数** @param paramsMap 参数* @return ReturnResult<Object>*/@PostMapping("/signPost")public ReturnResult<Object> signPost(@RequestBody Map<String, Object> paramsMap) {return signService.signPost(paramsMap);}
}

7.2 service层代码

package com.xhs.service;import com.xhs.message.ReturnResult;import java.util.Map;/*** @desc:* @projectName: java-tools-parent* @author: xhs* @date: 2023-8-27 027 16:36* @version: JDK 1.8*/
public interface SignService {/*** 生成签名 GET请求方式** @param paramsMap 参数* @return ReturnResult<Object>*/ReturnResult<Object> signGet(Map<String, Object> paramsMap);/*** 生成签名 POST请求方式** @param paramsMap 参数* @return ReturnResult<Object>*/ReturnResult<Object> signPost(Map<String, Object> paramsMap);
}

7.3 service实现层代码

package com.xhs.service.impl;import cn.dev33.satoken.sign.SaSignUtil;
import com.xhs.message.Result;
import com.xhs.message.ReturnResult;
import com.xhs.service.SignService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;import java.util.Map;/*** @desc:* @projectName: java-tools-parent* @author: xhs* @date: 2023-8-27 027 16:36* @version: JDK 1.8*/
@Slf4j
@Service
public class SignServiceImpl implements SignService {/*** 生成签名 POST请求方式** @param paramsMap 参数* @return ReturnResult<Object>*/@Overridepublic ReturnResult<Object> signGet(Map<String, Object> paramsMap) {log.info("生成签名的入参-paramsMap:{}", paramsMap);String signParams = SaSignUtil.addSignParamsAndJoin(paramsMap);log.info("生成签名后的参数-signParams:{}", signParams);return ReturnResult.build(Result.SUCCESS).setData(signParams);}/*** 生成签名 POST请求方式** @param paramsMap 参数* @return ReturnResult<Object>*/@Overridepublic ReturnResult<Object> signPost(Map<String, Object> paramsMap) {log.info("生成签名的入参-paramsMap:{}", paramsMap);Map<String, Object> map = SaSignUtil.addSignParams(paramsMap);log.info("生成签名后的参数-signParams:{}", map);return ReturnResult.build(Result.SUCCESS).setData(map);}
}

8.调用接口并校验签名是否合法
8.1 GET请求参数拼接到url后面

http://127.0.0.1:1000/tools/getName?name=admin&timestamp=1693145776820&nonce=kaTqdadO4u04hZG0gekEIvXmeN5QZD8A&sign=4b5f414e24290ed7766c2d79910264a7

在这里插入图片描述

在这里插入图片描述
8.2 POST请求 使用@RequestBody接受参数
在这里插入图片描述
在这里插入图片描述
注意

使用@RequestBody接受参数需要创建过滤器将请求体内容传递给下一个处理器,否则会报错
org.springframework.http.converter.HttpMessageNotReadableException: Required request body is missing:

在这里插入图片描述
解决方法:参照 第五步”5.创建HttpServletRequestFilter过滤器“

9.源码地址
https://gitee.com/xhs101/java-tools-parent

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://xiahunao.cn/news/1621954.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

LeetCode-406-根据身高重建队列

题目描述&#xff1a; 假设有打乱顺序的一群人站成一个队列&#xff0c;数组 people 表示队列中一些人的属性&#xff08;不一定按顺序&#xff09;。每个 people[i] [hi, ki] 表示第 i 个人的身高为 hi &#xff0c;前面 正好 有 ki 个身高大于或等于 hi 的人。 请你重新构造…

【其他】小红伞Avira,扫描压缩文件

1. 命令行执行小红伞Avira扫描指定文件 命令和参数如下 "小红伞扫描软件路径" /CFG"小红伞扫描辅助scan.avp路径" /PATH"待扫描文件路径" 举例:"C:\Program Files (x86)\Avira\Antivirus\avscan.exe" /CFG"C:\Program Files…

sql语句中的ddl和dml

操作数据库&#xff1a;CRUD C&#xff08;create&#xff09; 创建 *数据库创建出来默认字符集为utf8 如果要更改字符集就 Create database 名称 character set gbk&#xff08;字符集&#xff09; *创建数据库&#xff1a;create database 名称 *先检查是否有该数据库在…

字节律动之*你太美, emm 其实是个字符画雪花视频-哈哈哈-将视频转成一张张字符画图片

效果 整体效果 局部图片放大效果 视频转换后带雪花特效,凑合看吧, 视频地址 准备工作 安装FFmpeg 电脑上安装ffpeg处理视频并设置环境变量, windows可以参考FFmpeg的安装教程这篇博客安装 mac可以直接执行brew install ffmpeg安装 安装python依赖包 执行pip3 install -…

.NET 操作 TDengine .NET ORM

TDengine 是国内比较流的时序库之一&#xff0c;支持群集并且免费&#xff0c;在.NET中资料比较少&#xff0c;这篇文章主要介绍SqlSugar ORM来操作TDengine 优点&#xff1a; 1、SqlSugar支持ADO.NET操作来实现TDengine&#xff0c;并且支持了常用的时间函数、支持联表、分…

如何注册一个免费的网站空间和域名

https://www.jianshu.com/p/016c83f70d43 前几天想搞个网站玩玩&#xff0c;于是就上网搜哪里可以注册免费的网站空间和域名&#xff0c;折腾了好几天都没注册成&#xff0c;国内好像已经没有免费的了。只好用英文去搜国外的网站&#xff0c;才找到了一个能注册成功的&#xff…

MybatisPlus 项目中使用

大家好 , 我是苏麟 , 今天带来 MybatisPlus 的简单使用 . 官方网站 : MyBatis-Plus (baomidou.com) 开始使用 初步体验 引入依赖 <dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version…

探秘腾讯Android手机游戏平台之不安装游戏APK直接启动法

探秘腾讯Android手机游戏平台之不安装游戏APK直接启动法 2011年09月18日 ⁄ 移动开发 ⁄ 评论数 38 ⁄ 被围观 8,791 前言 相信这样一个问题&#xff0c;大家都不会陌生&#xff0c; “有什么的方法可以使Android的程序APK不用安装&#xff0c;而能够直接启动”。 发现最后的…

群玩小游戏sdk一键接入

群玩提供第三方用户互动内容服务&#xff0c;拥有“你画我猜”、“谁是卧底”、“豆腐王国”、“猜歌名” 等多款互动竞猜类游戏、智能机器人接入服务&#xff0c;适合直播、社交、社群行业&#xff0c;可以自定义设计玩法&#xff0c;页面精美有趣&#xff0c;接入方便快捷&am…

职业成长面对面交流会

职业成长面对面交流会 ——微软高级主管欧琼老师讲座2009年4月12日星期日上午十点半由电子工业出版社博文视点资讯有限公司隆重举办一场“职业成长面对面交流会”&#xff0c;主讲老师是《微软360度-成功和成长》主要作者 微软总部高级主管 欧琼老师&#xff0c;精彩的讲座在开…

游戏客户端面试(Unity)

推荐阅读&#xff1a; 我的CSDN 我的博客园 QQ群&#xff1a;704621321 我的个人博客 一。最开始的两家公司笔试面试题目 一家公司是学校聘请研究教育方面VR课件的公司&#xff0c;面试没几天&#xff0c;就收到了面试通过的消息&#xff0c;后面因为通过了另一家游戏公司而拒绝…

Unity - 微信小游戏

总参考&#xff1a;Unity WebGL 微信小游戏适配方案(公测) 安装与使用 下载 Unity插件&#xff0c;并导入至游戏项目中&#xff0c;版本更新请查看更新日志 请查阅推荐引擎版本&#xff0c;安装时选择WebGL组件 最终选择Unity2021.2.5f1c1InstantGame前往Node官网安装长期稳定…

聊天群内组局互动小游戏

“找我61”互动小游戏嵌入到聊天群内&#xff0c;会给社交应用&#xff08;产品&#xff09;增加不少人气&#xff0c;带来更多活力。 新颖社区社交产品“知多视广”架构构思设想

活动预告 | 与大神面对面,移动游戏开发者必须掌握的十大技巧

上半年&#xff0c;国内游戏下载量对比2019年下半年上涨将近50%&#xff0c;在用户旺盛的需求下&#xff0c;游戏行业迎来了绝佳的发展机遇。在市场火爆的背后&#xff0c;随着用户需求的多样化、时间的碎片化&#xff0c;一些因开发繁琐、资源不足的问题频频出现&#xff0c;让…

游戏大厅 从基础开始(1)——最简单的关系,用户与房间

游戏大厅 从基础开始&#xff08;1&#xff09; ——最简单的关系&#xff0c;用户与房间。 做游戏 就好像写一篇简单的记叙文 作为小学语文大纲要求 记叙文需要完整地叙述&#xff1a; 时间&#xff0c;地点&#xff0c;人物&#xff0c;事件 也就是 WHEN &#xff0c; WH…

RK3399平台开发系列讲解(存储篇)Linux 存储系统的 I/O 栈

平台内核版本安卓版本RK3399Linux4.4Android7.1🚀返回专栏总目录 文章目录 一、Linux 存储系统全景二、Linux 存储系统的缓存沉淀、分享、成长,让自己和他人都能有所收获!😄 📢本篇将介绍 Linux 存储系统的 I/O 原理。 一、Linux 存储系统全景 我们可以把 Linux 存储系…

解决MASM32代码汇编出错: error A2181: initializer must be a string or single item

最近用MASM32编程更新SysInfo&#xff0c;增加对IPv6连接信息的收集&#xff0c;使用到了 typedef struct _MIB_TCP6ROW_OWNER_MODULE {UCHAR ucLocalAddr[16];DWORD dwLocalScopeId;DWORD dwLocalPort;UCHAR ucRemoteAddr[16];DWORD …

文本分类任务

文章目录 引言1. 文本分类-使用场景2. 自定义类别任务3. 贝叶斯算法3.1 预备知识3.2 贝叶斯公式3.3 贝叶斯公式的应用3.4 贝叶斯公式在NLP中的应用3.5 贝叶斯公式-文本分类3.6 代码实现3.7 贝叶斯算法的优缺点 4. 支持向量机4.1 支持向量机-核函数4.2 支持向量机-解决多分类4.3…

android系统启动流程之zygote如何创建SystemServer进程

SystemServer:是独立的进程&#xff0c;主要工作是管理服务的&#xff0c;它将启动大约90种服务Services. 它主要承担的职责是为APP的运行提供各种服务&#xff0c;像AMS,WMS这些服务并不是一个独立的进程&#xff0c; 它们其实都是SystemServer进程中需要管理的的众多服务之一…