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

news2024/11/22 15:02:24

使用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());
    }

    @Override
    public BufferedReader getReader() throws IOException {
        return new BufferedReader(new InputStreamReader(getInputStream()));
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {
        final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes());
        return new ServletInputStream() {
            @Override
            public boolean isFinished() {
                return false;
            }

            @Override
            public boolean isReady() {
                return false;
            }

            @Override
            public void setReadListener(ReadListener readListener) {

            }

            @Override
            public int read() {
                return byteArrayInputStream.read();
            }
        };
    }

    @Override
    public String getParameter(String name) {
        String[] values = parameterMap.get(name);
        if (values != null && values.length > 0) {
            return values[0];
        }
        return null;
    }

    @Override
    public Map<String, String[]> getParameterMap() {
        return parameterMap;
    }

    @Override
    public Enumeration<String> getParameterNames() {
        return Collections.enumeration(parameterMap.keySet());
    }

    @Override
    public 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
     */
    @Override
    public 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
     */
    @Override
    public 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 {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        Filter.super.init(filterConfig);
    }

    @Override
    public 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 {

    @Resource
    private 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>
     */
    @Override
    public 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>
     */
    @Override
    public 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://www.coloradmin.cn/o/936774.html

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

相关文章

HCIP-HCS华为私有云

1、概述 HCS&#xff08;HuaweiCoudStack&#xff09;华为私有云&#xff1a;6.3 之前叫FusionSphere OpenStack&#xff0c;6.3.1 版本开始叫FusionCloud&#xff0c;6.5.1 版本开始叫HuaweiCloud Stack (HCS)华为私有云软件。 开源openstack&#xff0c;发放云主机的流程&am…

第五章 树与二叉树 一、树的定义与考点

一、定义 1.树是由n (n > 0) 个节点组成的有限集合。 2.当n0时&#xff0c;称为空树。 3.在非空树中&#xff0c;有且仅有一个节点没有前驱&#xff0c;其他节点都有且仅有一个前驱&#xff0c;称为根节点。 4.每个节点有零个或多个子节点&#xff0c;而每个子节点又有零…

多态/虚函数/虚函数表

OVERVIEW 多态/虚函数/虚函数表1.虚函数引入后类发生的变化&#xff1f;2.虚函数表的生成时机和生成原因&#xff1f;3.虚函数表指针赋值的时机&#xff1f;4.类对象在内存中的布局&#xff1f;5.虚函数的工作原理和多态性的体现&#xff1f;6.其他问题 多态/虚函数/虚函数表 n…

Android JNI系列详解之生成指定CPU的库文件

一、前提 这次主要了解Android的cpu架构类型&#xff0c;以及在使用CMake工具的时候&#xff0c;如何指定生成哪种类型的库文件。 如上图所示&#xff0c;是我们之前使用CMake工具默认生成的四种cpu架构的动态库文件&#xff1a;arm64-v8a、armeabi-v7a、x86、x86_64&#xff0…

昇腾Ascend+C编程入门教程(纯干货)

2023年5月6日&#xff0c;在昇腾AI开发者峰会上&#xff0c;华为正式发布了面向算子开发场景的昇腾Ascend C编程语言。Ascend C原生支持C/C编程规范&#xff0c;通过多层接口抽象、并行编程范式、孪生调试等技术&#xff0c;极大提高了算子的开发效率&#xff0c;帮助AI开发者低…

go学习之流程控制语句

文章目录 流程控制语句1.顺序控制2.分支控制2.1单分支2.2双分支单分支和双分支的四个题目switch分支结构 3.循环控制for循环控制while 和do...while的实现 4.跳转控制语句breakcontinuegotoreturngotoreturn 流程控制语句 介绍&#xff1a;在程序中&#xff0c;程序运行的流程…

星际争霸之小霸王之小蜜蜂(七)--消失的子弹

目录 前言 一、删除子弹 二、限制子弹数量 三、继续重构代码 总结 前言 昨天我们已经让子弹飞了起来&#xff0c;但是会面临一个和之前小蜜蜂一样的问题&#xff0c;小蜜蜂的行动应该限制在窗口内&#xff0c;那么子弹也是有相同之处&#xff0c;也需要限制一个移动范围&…

智慧监狱整体解决方案PPT

导读&#xff1a;原文《智慧监狱整体解决方案PPT》&#xff08;获取来源见文尾&#xff09;&#xff0c;本文精选其中精华及架构部分&#xff0c;逻辑清晰、内容完整&#xff0c;为快速形成售前方案提供参考。 喜欢文章&#xff0c;您可以点赞评论转发本文&#xff0c;了解更多…

全球互联网裁员下测试人员何去何从?

时间好像突然加快了步伐瞬间觉得匆匆&#xff0c;转眼已经23年&#xff0c;从20年到23年。回想起来恍恍惚惚&#xff0c;疫情中经历的种种就好像没有发生过一样&#xff0c;很多的魑魅魍魉荒唐可笑真实又虚幻&#xff0c;时光向前人生向后&#xff0c;那些魔幻的人和事也慢慢消…

可解释性的相关介绍

一、可解释性的元定义&#xff08;Meta-definitions of Interpretability&#xff09; The extent to which an individual can comprehend the cause of a model’s outcome. [1]The degree to which a human can consistently predict a model’s outcome. [2] 可解释性&am…

Flutter 项目结构文件

1、Flutter项目的文件结构 先helloworld项目&#xff0c;看看它都包含哪些组成部分。首先&#xff0c;来看一下项目的文件结构&#xff0c;如下图所示。 2、介绍上图的内容。 -litb/main.dart文件&#xff1a;整个应用的入口文件&#xff0c;其中的main函数是整个Flutter应…

Fei-Fei Li-Lecture 16:3D Vision 【斯坦福大学李飞飞CV课程第16讲:3D Vision】

目录 P1 2D Detection and Segmentation​编辑 P2 Video 2D time series P3 Focus on Two Problems P4 Many more topics in 3D Vision P5-10 Multi-View CNN P11 Experiments – Classification & Retrieval P12 3D Shape Representations P13--17 3D Shape Rep…

【集合学习ConcurrentHashMap】ConcurrentHashMap集合学习

ConcurrentHashMap集合学习 一、JDK1.7 和 1.8 版本ConcurrenHashMap对比分析 JDK 1.7版本 在JDK 1.7版本ConcurrentHashMap使用了分段锁的方式&#xff08;对Segment进行加锁&#xff09;&#xff0c;其实际结构为&#xff1a;Segment数组 HashEntry数组 链表。由很多个 …

蓝蓝设计ui设计公司作品案例-中节能现金流抗压测试软件交互及界面设计

中国节能是以节能环保为主业的中央企业。中国节能以生态文明建设为己任&#xff0c;长期致力于让天更蓝、山更绿、水更清&#xff0c;让生活更美好。经过多年发展&#xff0c;中国节能已构建起以节能、环保、清洁能源、健康和节能环保综合服务为主业的41产业格局&#xff0c;成…

计算机组成原理 | 第一章 计算机系统概述

目录 计算机发展历程 计算机系统层次结构 计算机的性能指标 计算机发展历程 电子计算机的发展已经历了4代&#xff0c;这4代计算机的主要元件分别是电子管、晶体管、中小规模集成电路、大规模集成电路。微型计算机的发展以微处理器技术为标志。可以在计算机中直接执行的语…

【Docker系列】Docker-核心概念/常用命令与项目部署实践

写在前面 Docker是一种开源的容器化技术&#xff0c;它允许开发者将应用程序及其依赖项打包到一个轻量级、可移植的容器中&#xff0c;从而实现快速部署和高效运行。Docker的核心概念包括镜像、容器、仓库等。本文将详细介绍Docker的基本概念、安装方法以及常用命令。 一、Doc…

第 360 场 LeetCode 周赛题解

A 距离原点最远的点 串中的 “_” 处要么都向左走要么都向右走 class Solution { public:int furthestDistanceFromOrigin(string moves) {int t 0;for (auto x: moves)if (x ! R)t--;elset;int res abs(t);t 0;for (auto x: moves)if (x ! L)t;elset--;res max(res, abs(t…

土豆叶病害识别(Python代码,pyTorch框架,深度卷积网络模型,很容易替换为其它模型,视频识别)

效果视频&#xff1a;土豆叶病害识别&#xff08;Python代码&#xff0c;pyTorch框架&#xff0c;视频识别&#xff09;_哔哩哔哩_bilibili 代码运行要求&#xff1a;Torch库>1.13.1&#xff0c;其它库无版本要求 1..土豆叶数据集主要包好三种类别&#xff08;Early_Bligh…

集丰照明|汽车美容店设计,装修色彩灯光搭配方法

正确处理好店面的空间设计。 店铺各个功能区设计要合理&#xff0c;衔接合理&#xff0c;这样既能提高员工的工作效率也能提高顾客的满意度。合理安排店铺的空间分配&#xff0c; 要给顾客一种舒适度&#xff0c;既不能让顾客感觉到过于拥挤&#xff0c;又不能浪费店铺的有限空…