Spring Boot实现接口签名验证的过程

 更新时间:2024年04月26日 09:48:05   作者:涛哥是个大帅比  
在Spring Boot中实现接口校验签名通常是为了保证接口请求的安全性和数据的完整性,这篇文章主要介绍了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'

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

源码百度网盘下载地址:

链接: https://pan.baidu.com/s/1dhtbspb3AC3M_sWBG2D-Xg?pwd=k57m

提取码: k57m

到此这篇关于Spring Boot实现接口签名验证的文章就介绍到这了,更多相关Spring Boot接口签名验证内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • springboot整合druid及多数据源配置的demo

    springboot整合druid及多数据源配置的demo

    这篇文章主要介绍了springboot整合druid及多数据源配置的demo,本篇主要分两部分 ①springboot整合druid的代码配置,以及druid的监控页面演示;②对实际场景中多数据源的配置使用进行讲解,需要的朋友可以参考下
    2024-01-01
  • BigDecimal的加减乘除计算方法详解

    BigDecimal的加减乘除计算方法详解

    小编做题遇到了大数的精确计算,再次认识了bigdecimal关于Bigdecimal意外的有许多小知识点和坑,这里特此整理一下为方便以后学习,希望能帮助到其他的萌新
    2021-08-08
  • 通过实例解析java过滤器和拦截器的区别

    通过实例解析java过滤器和拦截器的区别

    这篇文章主要介绍了通过实例解析java过滤器和拦截器的区别,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-03-03
  • IDEA常量字符串过长问题及解决方案

    IDEA常量字符串过长问题及解决方案

    在编译Java项目时遇到“常量字符串过长”错误,可以通过修改编译器设置解决,具体方法是进入IDE的设置(File>>Settings>>Build, Execution, Deployment>>Compiler>>Java Compiler),将使用的编译器更改为Eclipse,如果问题依旧
    2024-10-10
  • springboot自定义拦截器的方法

    springboot自定义拦截器的方法

    这篇文章主要为大家详细介绍了springboot自定义拦截器的方法,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-01-01
  • Spring Boot2深入分析解决java.lang.ArrayStoreException异常

    Spring Boot2深入分析解决java.lang.ArrayStoreException异常

    这篇文章介绍了Spring Boot2深入分析解决java.lang.ArrayStoreException异常的方法,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-12-12
  • Mybatis使用XML实现动态sql的示例代码

    Mybatis使用XML实现动态sql的示例代码

    当编写 MyBatis 中复杂动态 SQL 语句时,使用 XML 格式是一种非常灵活的方式,本文主要为大家详细介绍了Mybatis使用XML实现动态sql的具体方法,需要的可以参考下
    2023-12-12
  • 使用IDEA和Gradle构建Vertx项目(图文步骤)

    使用IDEA和Gradle构建Vertx项目(图文步骤)

    这篇文章主要介绍了使用IDEA和Gradle构建Vertx项目(图文步骤),小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-09-09
  • Spring Cloud Sleuth 和 Zipkin 进行分布式跟踪使用小结

    Spring Cloud Sleuth 和 Zipkin 进行分布式跟踪使用小结

    分布式跟踪是一种机制,我们可以使用它跟踪整个分布式系统中的特定请求,分布式跟踪允许您跟踪分布式系统中的请求,本文给大家介绍Spring Cloud Sleuth 和 Zipkin 进行分布式跟踪使用小结,感兴趣的朋友一起看看吧
    2022-03-03
  • Springboot整合Redis的详细教程分享

    Springboot整合Redis的详细教程分享

    这篇文章主要为大家详细介绍了如何利用SpringBoot整合Redis,文中的示例代码讲解详细,具有很好的参考价值,希望对大家有所帮助
    2022-08-08

最新评论