Spring Boot实现接口签名验证

项目场景:

        开放接口是指不需要登录凭证就允许被第三方系统调用的接口。为了防止开放接口被恶意调用,开放接口一般都需要验签才能被调用。

        在Spring Boot中实现接口校验签名通常是为了保证接口请求的安全性和数据的完整性。签名校验通常涉及对请求参数的签名计算和验证,以确保请求是由可信的发送方发送,并且在传输过程中没有被篡改。下面,我将详细介绍如何在Spring Boot应用中实现接口校验签名的过程。


解决方案:

1.配置签名密钥

签名密钥是用于生成和验证签名的秘密信息,生成规则自己定义,需要把生成的密钥提供给第三方。

比如:

appId:360aa3a3ba074da6a7bb17ae55e72d26
appSecret:81343DC5-6E80-483A-A427-E3DF5FA4E5F3

/**
 * 生成应用id和密钥
 */
@RequestMapping(value = "/getSecret", method = RequestMethod.GET)
public Map<String, String> getSecret() {
	//生成应用id和密钥提供给第三方使用,具体生成规则自己定
	String appId = UUID.randomUUID().toString().replace("-", "").toLowerCase();
	String appSecret = UUID.randomUUID().toString().toUpperCase();
	System.out.println("appId:"+appId);
	System.out.println("appSecret:"+appSecret);

	Map<String, String> map = new HashMap<>();
	map.put("appId", appId);
	map.put("appSecret", appSecret);
	return map;
}

 2.定义签名算法

        一个用于生成签名,另一个用于验证签名。生成签名的方法通常将请求参数按照特定规则计算出一个签名值。常见的签名算法有HMAC-SHA1、HMAC-SHA256等。

        验证签名的方法则是对接收到的请求参数进行同样的处理,并计算出一个签名值,然后与请求中携带的签名值进行比对。

package com.test.utils;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;

public class Signature {
    /**
     * 获取签名
     * @param secretKey 密钥
     * @param data  需要签名的数据
     * @return  签名
     */
    public static String signWithHmacSha1(String secretKey, String data) {

        try {
            SecretKeySpec signingKey = new SecretKeySpec(secretKey.getBytes("UTF-8"), "HmacSHA1");
            Mac mac = Mac.getInstance("HmacSHA1");
            mac.init(signingKey);
            return Base64.getEncoder().encodeToString(mac.doFinal(data.getBytes("UTF-8")));
        } catch (NoSuchAlgorithmException | InvalidKeyException | UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 验证签名
     * @param secretKey 密钥
     * @param data  需要签名的数据
     * @param hmac  已经签名的数据
     * @return  true:签名一致
     */
    public static boolean verify(String secretKey, String data, String hmac) {
        String calculatedHmac = signWithHmacSha1(secretKey, data);
        return calculatedHmac.equals(hmac);
    }
}

3.拦截器或过滤器实现

使用Spring的拦截器(Interceptor)或过滤器(Filter)来实现对接口请求的签名校验。在拦截器或过滤器中,你可以获取到请求的参数,并调用签名验证方法来校验签名的有效性。

package com.test.aop;

import com.test.utils.Signature;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 签名拦截器
 */
@Component
@Slf4j
public class SignInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        response.setCharacterEncoding("UTF-8");
        response.setContentType("text/html;charset=UTF-8");

        //分配的应用id
        String appId = request.getHeader("appId");
        //时间戳
        String timestampStr = request.getHeader("timestamp");
        //签名
        String signature = request.getHeader("signature");
        if(StringUtils.isBlank(appId) || StringUtils.isBlank(timestampStr) || StringUtils.isBlank(signature)){
            response.setStatus(500);
            response.getWriter().println("参数错误!");
            return false;
        }

        //这个密钥实际应该根据appId到数据库里查出来
        String appSecret = "81343DC5-6E80-483A-A427-E3DF5FA4E5F3";
        //如果密钥没查到
//        if(flag){
//            response.setStatus(500);
//            response.getWriter().println("密钥不存在!");
//        }

        //拼接数据
        String origin = appId + "\n" + appSecret + "\n" + timestampStr;
        if(!Signature.verify(appSecret, origin, signature)){
            response.setStatus(500);
            response.getWriter().println("签名错误!");
            return false;
        }

        //业务的时间戳
        long timestamp = Long.parseLong(timestampStr);
        //当前时间戳
        long currentTimestamp = System.currentTimeMillis() / 1000;
        //10分钟内有效
        long timeDifference = 10 * 60;
        if(Math.abs(timestamp - currentTimestamp) > timeDifference){
            response.setStatus(500);
            response.getWriter().println("签名过期!");
            return false;
        }

        //放行
        return true;
    }
}

配置拦截器

package com.test.config;

import com.test.aop.SignInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;

/**
 * WebMvc配置
 */
@Configuration
public class WebMvcConfig extends WebMvcConfigurationSupport {
	@Resource
	private SignInterceptor signInterceptor;

	@Override
	public void addInterceptors(InterceptorRegistry registry) {
		//不拦截的地址
		List<String> excludedList = new ArrayList<>();
		//swagger地址
		excludedList.add("/swagger-ui.html");
		excludedList.add("/swagger-ui.html/**");
		excludedList.add("/webjars/**");
		excludedList.add("/swagger/**");
		excludedList.add("/doc.html");
		excludedList.add("/doc.html/**");
		excludedList.add("/swagger-resources/**");
		excludedList.add("/v2/**");
		excludedList.add("/favicon.ico");
		//生成应用id和密钥接口不拦截
		excludedList.add("/getSecret");


		registry.addInterceptor(signInterceptor)
				.addPathPatterns("/**")//拦截所有请求
				.excludePathPatterns(excludedList);//排除的请求
		super.addInterceptors(registry);
	}
}

4.测试接口 

controller定义一个测试接口

/**
 * 测试签名
 */
@RequestMapping(value = "/sign", method = RequestMethod.GET)
public String sign() {
	return "success";
}

模拟第三方调用: 

我们需要把接口请求头所需参数和签名的方法告知第三方。

 接口请求头参数如下:

参数名称中文参数值
appId应用id360aa3a3ba074da6a7bb17ae55e72d26
timestamp当前时间戳,精确到秒

1713838208

signature签名bjvXebFiHi2+I93BNs+8+Tl2I7k=

签名方法: 

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;

public class Signature {
    /**
     * 获取签名
     * @param secretKey 密钥
     * @param data  需要签名的数据
     * @return  签名
     */
    public static String signWithHmacSha1(String secretKey, String data) {

        try {
            SecretKeySpec signingKey = new SecretKeySpec(secretKey.getBytes("UTF-8"), "HmacSHA1");
            Mac mac = Mac.getInstance("HmacSHA1");
            mac.init(signingKey);
            return Base64.getEncoder().encodeToString(mac.doFinal(data.getBytes("UTF-8")));
        } catch (NoSuchAlgorithmException | InvalidKeyException | UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return null;
    }
}

 生成签名示例:

public static void main(String[] args) {
   //这里的应用id和密钥已实际分配的为准
   String appId = "360aa3a3ba074da6a7bb17ae55e72d26";
   String appSecret = "81343DC5-6E80-483A-A427-E3DF5FA4E5F3";
   //当前时间戳,精确到秒,示例:1713838208
   long timestamp = System.currentTimeMillis() / 1000;
   //拼接数据:appId、appSecret、timestamp
   String origin = appId + "\n" + appSecret + "\n" + timestamp;
   String signature = Signature.signWithHmacSha1(appSecret, origin);

   //需要加到请求头的参数
   System.out.println("appId:"+appId);
   System.out.println("timestamp:"+timestamp);
   System.out.println("signature:"+signature);
}

 当第三方知道接口请求头所需参数和签名的方法后,就可以调用接口了

curl调用:

curl -X GET \
  http://localhost:8080/testservice/test/sign \
  -H 'appId: 360aa3a3ba074da6a7bb17ae55e72d26' \
  -H 'signature: bjvXebFiHi2+I93BNs+8+Tl2I7k=' \
  -H 'timestamp: 1713843128'

总结

  1. 这里签名由appId + "\n" + appSecret + "\n" + timestamp,生成签名字串
  2. 时间戳用于保证签名的有效性,即使签名被盗用,也只能在有效时间内使用
  3. appId、appSecret自己定义生成规则,保存到数据库中

源码:https://download.csdn.net/download/u011974797/89211980

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

泰坦尼克号乘客生存情况预测分析2

泰坦尼克号乘客生存情况预测分析1 泰坦尼克号乘客生存情况预测分析2 泰坦尼克号乘客生存情况预测分析3 泰坦尼克号乘客生存情况预测分析总 背景描述 Titanic数据集在数据分析领域是十分经典的数据集&#xff0c;非常适合刚入门的小伙伴进行学习&#xff01; 泰坦尼克号轮船的…

AI新闻速递:揭秘本周科技界最热的AI创新与发展

兄弟朋友们&#xff0c;本周的AI领域又迎来了一系列激动人心的进展。在这个快速变化的时代&#xff0c;不会利用AI的人&#xff0c;就像在数字化高速公路上步行的旅行者&#xff0c;眼看着同行者驾驶着智能汽车绝尘而去&#xff0c;而自己却束手无策。 1. Adobe Firefly 3&…

【基础算法总结】双指针算法二

双指针 1.有效三角形的个数2.和为S的两个数字3.和为S的两个数字4.四数之和 点赞&#x1f44d;&#x1f44d;收藏&#x1f31f;&#x1f31f;关注&#x1f496;&#x1f496; 你的支持是对我最大的鼓励&#xff0c;我们一起努力吧!&#x1f603;&#x1f603; 1.有效三角形的个数…

深度学习运算:CUDA 编程简介

一、说明 如今&#xff0c;当我们谈论深度学习时&#xff0c;通常会将其实现与利用 GPU 来提高性能联系起来。GPU&#xff08;图形处理单元&#xff09;最初设计用于加速图像、2D 和 3D 图形的渲染。然而&#xff0c;由于它们能够执行许多并行操作&#xff0c;因此它们的实用性…

Python游戏工具包pygame

当你涉及游戏开发时&#xff0c;Pygame是一个强大的工具包&#xff0c;它提供了一系列功能丰富的模块和工具&#xff0c;让你可以轻松地创建各种类型的游戏。在本文中&#xff0c;我将介绍Pygame的依赖以及其详细属性&#xff0c;同时提供一些示例代码来说明其用法。 目录 一…

Github 2024-04-27 开源项目日报 Top9

根据Github Trendings的统计,今日(2024-04-27统计)共有9个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量Python项目6TypeScript项目2C++项目1JavaScript项目1Open-Sora: 构建自己的视频生成模型 创建周期:17 天开发语言:Python协议类型:Apache Lic…

嵌入式Linux学习——Linux常用命令(上)

Linux命令行介绍 Linux Shell 简介 Shell 的意思是“外壳”&#xff0c;在 Linux 中它是一个程序&#xff0c;比如/bin/sh、/bin/bash 等。它负责接收用户的输入&#xff0c;根据用户的输入找到其他程序并运行。比如我们输入“ ls”并回车时&#xff0c; shell 程序找到“ ls…

TinyML之Hello world----基于Arduino Nano 33 BLE Sense Rev2的呼吸灯

早期版本的Hello World 这应该是一个逼格比较高的呼吸灯了&#xff0c;用ML来实现呼吸灯功能&#xff0c;之前已经有大佬发过类似的文章&#xff1a;https://blog.csdn.net/weixin_45116099/article/details/126310816 当前版本的Hello World 这是一个ML的入门例程&#xff…

黑马程序员C++学习总结【进阶篇】

本阶段主要针对C泛型编程和STL技术做详细讲解&#xff0c;探讨C更深层的使用 黑马程序员C学习总结【基础篇】 黑马程序员C学习总结【核心篇】 黑马程序员C学习总结【进阶篇】 黑马程序员C学习总结【进阶篇】 一、模板1.函数模板&#xff08;1&#xff09;函数模板2种使用方式&a…

重学java 25.面向对象 权限修饰符、final关键字、代码块

别让平淡生活&#xff0c;耗尽你所有的向往 —— 24.4.27 重点概述 01.知道final修饰成员之后特点 02.会使用静态代码块以及知道静态代码块的使用场景 03.会使用匿名内部类 一、权限修饰符 1.概述 在Java中提供了四种访问权限&#xff0c;使用不同的访问权限修饰符修饰时&#…

为什么 Facebook 不使用 Git?

在编程的世界里&#xff0c;Git 就像水一样常见&#xff0c;以至于我们认为它是创建和管理代码更改的唯一可行的工具。 前 Facebook 员工&#xff0c;2024 年 首先&#xff0c;我为什么关心&#xff1f; 我致力于构建 Graphite&#xff0c;它从根本上受到 Facebook 内部工具的…

第十五届蓝桥杯省赛第二场C/C++B组E题【遗迹】题解

解题思路 错解 贪心&#xff1a;每次都移动至当前最近的对应方块上。 反例&#xff1a; s s s abxac t t t abac 贪心结果&#xff08;下标&#xff09; 0 → 1 → 0 → 4 0 \rightarrow 1 \rightarrow 0 \rightarrow 4 0→1→0→4&#xff0c;答案为 5 5 5。 正确结…

【MRI重建】基于径向采样的GRASP重建实现(matlab)

关于 对比增强MRI和弥散MRI成像,对于时间分辨率要求都比较高,为了捕获高时间空间分辨率,这里使用GRASP方法,重建radial径向采样的MR数据。使用的稀疏正则项为 temporal total variation。 相关文章 https://onlinelibrary.wiley.com/doi/10.1002/mrm.24980 https://onl…

前端学习笔记3

列表、表格与表单​ 列表就是信息资源的一种展示形式。它可以使信息结构化和条理化,并以列表的样式显示出来,以便浏览者能更快捷地获得相应的信息。 3.0 代码访问地址 https://gitee.com/qiangge95243611/java118/tree/master/web/day03 3.1 列表 ​ 列表大致可以分为3类…

mac资源库的东西可以删除吗?提升Mac运行速度秘籍 Mac实用软件

很多小伙伴在使用mac电脑处理工作的时候&#xff0c;就会很疑惑&#xff0c;电脑的运行速度怎么越来越慢&#xff0c;就想着通过删除mac资源库的东西&#xff0c;那么mac资源库的东西可以删除吗&#xff1f;删除了会不会造成电脑故障呢&#xff1f; 首先&#xff0c;mac资源库…

沉浸式推理乐趣:体验线上剧本杀小程序的魅力

在这个信息爆炸的时代&#xff0c;人们的娱乐方式也在不断地推陈出新。其中&#xff0c;线上剧本杀小程序以其独特的沉浸式推理乐趣&#xff0c;成为了许多人的新宠。它不仅让我们在闲暇之余享受到了推理的快乐&#xff0c;更让我们在虚拟的世界里感受到了人性的复杂与多彩。 线…

【hackmyvm】 Quick2靶机

渗透流程 渗透开始1.IP地址 获取2.端口扫描3.任意文件读取4.扫描目录5.总结信息6.漏洞扫描7.php_filter_chain_generator.py使用8.提权 渗透开始 1.IP地址 获取 ┌─[✗]─[userparrot]─[~] └──╼ $fping -ag 192.168.9.0/24 2>/dev/null 192.168.9.124 本机 192.1…

base64格式图片直接显示

<img :src"data:image/png;base64,url"/>

阿斯达年代记游戏下载教程 阿斯达年代记下载教程

《阿斯达年代记&#xff1a;三强争霸》作为一款气势恢宏的MMORPG大作&#xff0c;是Netmarble与STUDIO DRAGON强强联合的巅峰创作&#xff0c;定于4月24日迎来全球玩家热切期待的公测。游戏剧情围绕阿斯达大陆的王权争夺战展开&#xff0c;三大派系——阿斯达联邦、亚高联盟及边…

“PowerInfer:消费级GPU上的高效大语言模型推理引擎“

PowerInfer是由上海交通大学IPADS实验室开发的一个高效大语言模型&#xff08;LLM&#xff09;推理引擎&#xff0c;专为个人电脑&#xff08;PC&#xff09;上的消费者级GPU设计。它通过利用LLM推理中的高局部性&#xff0c;实现了快速且资源消耗低的模型推理&#xff0c;这一…
最新文章