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 | 应用id | 360aa3a3ba074da6a7bb17ae55e72d26 |
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接口签名验证内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
相关文章
Spring Boot2深入分析解决java.lang.ArrayStoreException异常
这篇文章介绍了Spring Boot2深入分析解决java.lang.ArrayStoreException异常的方法,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧2021-12-12Spring Cloud Sleuth 和 Zipkin 进行分布式跟踪使用小结
分布式跟踪是一种机制,我们可以使用它跟踪整个分布式系统中的特定请求,分布式跟踪允许您跟踪分布式系统中的请求,本文给大家介绍Spring Cloud Sleuth 和 Zipkin 进行分布式跟踪使用小结,感兴趣的朋友一起看看吧2022-03-03
最新评论