SpringBoot中接口签名防止接口重放

一、接口签名的作用与实现要点


1. 为什么需要接口签名?

  • 目的:防止请求伪造。
  • 伪造风险:第三方可模拟合法请求,执行敏感操作(如转账)。
  • 签名作用:确保请求来源真实、数据未被篡改。

2. 如何实现接口签名?

  • 共享密钥:客户端与服务端约定一个保密的 secretKey。
  • 生成签名:使用 secretKey + 请求体 + 其他参数(如 nonce、timestamp)通过安全算法(如 HMAC-MD5)生成签名。
  • 传输签名:将签名放入请求头中发送。
  • 服务端验证:服务端按相同规则重新计算签名,比对一致性。

3. 防止请求伪造

  • 原理:攻击者无法获取 secretKey,无法生成有效签名,请求被拒绝。

4. 防止请求重放

  • 定义:攻击者截获并重复发送旧请求,冒充合法用户。
  • 解决方法
    • 使用唯一随机值 nonce,服务端记录已使用的 nonce(如 Redis),防止重复使用。
    • 引入 timestamp,限定请求在时间窗口内有效(如 5 分钟)。

二、方案实战

1. AccountController账户控制类

@RestController
@Slf4j
public class AccountController {
   
   
    @RequestMapping("/account/transfer")
    public ResponseResult<String> transfer(@RequestBody TransferAccount request) {
   
   
        log.info("转账成功:{}", JSONUtil.toJsonStr(request));
        return ResponseResult.success("转账成功");
    }
}

2. 自定义request包装类:可重用的主体请求包装器

public class ReusableBodyRequestWrapper extends HttpServletRequestWrapper {
   
   

    /**
     * -- GETTER --
     *  获取请求体的字节数组
     *
     * @return 请求体的字节数组
     */
    //参数字节数组,用于存储请求体的字节数据
    @Getter
    private byte[] requestBody;

    //Http请求对象
    private HttpServletRequest request;

    /**
     * 构造函数,初始化包装类
     *
     * @param request 原始HttpServletRequest对象
     * @throws IOException 如果读取请求体时发生IO错误
     */
    public ReusableBodyRequestWrapper(HttpServletRequest request) throws IOException {
   
   
        super(request);
        this.request = request;
    }

    /**
     * 重写getInputStream方法,实现请求体的重复读取
     *
     * @return 包含请求体数据的ServletInputStream对象
     * @throws IOException 如果读取请求体时发生IO错误
     */
    @Override
    public ServletInputStream getInputStream() throws IOException {
   
   
        /**
         * 每次调用此方法时将数据流中的数据读取出来,然后再回填到InputStream之中
         * 解决通过@RequestBody和@RequestParam(POST方式)读取一次后控制器拿不到参数问题
         */
        // 仅当requestBody未初始化时,从请求中读取并存储到requestBody
        if (null == this.requestBody) {
   
   
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            IOUtils.copy(request.getInputStream(), baos);
            this.requestBody = baos.toByteArray();
        }
        // 创建一个 ByteArrayInputStream 对象,用于重复读取requestBody
        final ByteArrayInputStream bais = new ByteArrayInputStream(requestBody);
        return new ServletInputStream() {
   
   

            @Override
            public boolean isFinished() {
   
   
                // 始终返回false,表示数据流未完成
                return false;
            }

            @Override
            public boolean isReady() {
   
   
                // 始终返回false,表示数据流未准备好
                return false;
            }

            @Override
            public void setReadListener(ReadListener listener) {
   
   
                // 不执行任何操作,因为该数据流不支持异步操作
            }

            
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值