🧑 博主简介:历代文学网(PC端可以访问:https://literature.sinhy.com/#/literature?__c=1000,移动端可微信小程序搜索“历代文学”)总架构师,
15年
工作经验,精通Java编程
,高并发设计
,Springboot和微服务
,熟悉Linux
,ESXI虚拟化
以及云原生Docker和K8s
,热衷于探索科技的边界,并将理论知识转化为实际应用。保持对新技术的好奇心,乐于分享所学,希望通过我的实践经历和见解,启发他人的创新思维。在这里,我希望能与志同道合的朋友交流探讨,共同进步,一起在技术的世界里不断学习成长。
Spring Boot 基于HmacSHA256算法实现API签名验证
在现代Web应用中,API的安全性至关重要。接口签名验证是一种常见的安全机制,用于确保请求的完整性和真实性。本文将详细介绍如何在Spring Boot
应用中实现接口签名验证,并提供真实典型的代码示例。我们将使用HmacSHA256
算法进行签名生成和验证。
HmacSHA256 算法简介
HmacSHA256 算法是一种消息认证码算法,它是 SHA-256 算法的变形版。通过使用密钥和散列函数,确保消息的认证性和完整性,防止消息被篡改。它广泛应用于网络安全领域,如 HTTPS 等协议。
HmacSHA256 算法具有安全性高、实用性强、易于实现、灵活性高和抗碰撞能力强等优点。在进行 HmacSHA256 算法运算时,需要传递密钥和数据两个参数,通过特定的方法将密钥转换成一个值,再与数据进行异或运算,并使用 SHA-256
哈希函数进行处理,得到一个 256 位的哈希值作为结果。
HmacSHA256 算法优点:
- 安全性高:通过使用密钥和散列函数,确保消息的认证性和完整性,防止消息被篡改。
- 实用性强:广泛应用于网络安全领域,如 HTTPS 等协议。
- 易于实现:实现过程相对简单,只需使用对应的加密库。
- 灵活性高:可将不同的散列函数和密钥组合使用,提高安全性。
- 抗碰撞能力强:具有较大的哈希值长度,减小了碰撞的概率。
一、接口签名验证原理
接口签名验证的核心思想是通过对请求参数进行加密生成签名,并将签名附加到请求中。服务器端接收到请求后,使用相同的算法对请求参数进行加密,并与请求中的签名进行比对。如果一致,则认为请求是合法的;否则,请求将被拒绝。
1.1 签名生成步骤
- 排序参数:将请求参数按字典序排序。
- 拼接参数:将排序后的参数按特定格式拼接成字符串。
- 加密签名:使用HmacSHA256算法对拼接后的字符串进行加密生成签名。
- 附加签名:将生成的签名附加到请求中。
1.2 签名验证步骤
- 获取请求参数:从请求中提取所有参数。
- 排序参数:将请求参数按字典序排序。
- 拼接参数:将排序后的参数按特定格式拼接成字符串。
- 加密签名:使用HmacSHA256算法对拼接后的字符串进行加密生成签名。
- 比对签名:将生成的签名与请求中的签名进行比对。
二、Spring Boot 实现接口签名验证
在Spring Boot中,我们可以通过自定义过滤器或拦截器来实现接口签名验证。以下是一个典型的实现示例。
2.1 创建签名工具类
首先,我们需要创建一个工具类来生成和验证签名。
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Base64;
import java.util.Map;
public class SignUtil {
private static final String SECRET_KEY = "your_secret_key"; // 密钥
private static final String HMAC_SHA256 = "HmacSHA256";
/**
* 生成签名
* @param params 请求参数
* @return 签名
*/
public static String generateSign(Map<String, String> params) {
// 按字典序排序
String[] sortedKeys = params.keySet().toArray(new String[0]);
Arrays.sort(sortedKeys);
// 拼接参数
StringBuilder paramStr = new StringBuilder();
for (String key : sortedKeys) {
paramStr.append(key).append(params.get(key));
}
// 拼接密钥
paramStr.append(SECRET_KEY);
// 生成签名
return Base64.getEncoder().encodeToString(hmacSha256(paramStr.toString().getBytes(StandardCharsets.UTF_8), SECRET_KEY.getBytes(StandardCharsets.UTF_8)));
}
/**
* 验证签名
* @param params 请求参数
* @param sign 请求中的签名
* @return 签名是否有效
*/
public static boolean verifySign(Map<String, String> params, String sign) {
if (sign == null || sign.isEmpty()) {
return false;
}
String generatedSign = generateSign(params);
return generatedSign.equals(sign);
}
/**
* 使用HmacSHA256算法生成签名
* @param data 数据
* @param key 密钥
* @return 签名
*/
private static byte[] hmacSha256(byte[] data, byte[] key) {
try {
Mac mac = Mac.getInstance(HMAC_SHA256);
mac.init(new SecretKeySpec(key, HMAC_SHA256));
return mac.doFinal(data);
} catch (Exception e) {
throw new RuntimeException("Failed to generate HMAC SHA256", e);
}
}
}
2.2 创建拦截器
接下来,我们创建一个拦截器来拦截请求并进行签名验证。
import org.springframework.stereotype.Component;
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;
@Component
public class SignInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 获取请求参数
Map<String, String> params = new HashMap<>();
Enumeration<String> paramNames = request.getParameterNames();
while (paramNames.hasMoreElements()) {
String paramName = paramNames.nextElement();
params.put(paramName, request.getParameter(paramName));
}
// 获取请求中的签名
String sign = request.getParameter("sign");
// 验证签名
if (!SignUtil.verifySign(params, sign)) {
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
response.getWriter().write("Invalid signature");
return false;
}
return true;
}
}
2.3 配置拦截器
最后,我们需要在Spring Boot中配置拦截器。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private SignInterceptor signInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(signInterceptor)
.addPathPatterns("/**"); // 拦截所有路径
}
}
三、Maven 依赖
为了确保以上代码示例能够正常运行,我们需要在 pom.xml
中添加以下依赖:
<dependencies>
<!-- Spring Boot Starter Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Apache Commons Codec -->
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.15</version>
</dependency>
</dependencies>
四、测试代码示例
为验证api签名功能正常,特意编写了一个简单的测试控制器和测试代码。
4.1 测试控制器
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
@RestController
public class TestController {
@GetMapping("/test")
public String test(@RequestParam Map<String, String> params) {
return "Request is valid";
}
@GetMapping("/generateSign")
public String generateSign(@RequestParam Map<String, String> params) {
return SignUtil.generateSign(params);
}
}
4.2 测试代码
我们可以使用Postman或其他HTTP客户端工具来测试接口。
-
生成签名:
- 请求URL:
http://localhost:8080/generateSign
- 请求参数:
param1=value1¶m2=value2
- 获取生成的签名。
- 请求URL:
-
验证签名:
- 请求URL:
http://localhost:8080/test
- 请求参数:
param1=value1¶m2=value2&sign=生成的签名
- 验证返回结果是否为
Request is valid
。
- 请求URL:
五、总结
通过以上步骤,我们成功在Spring Boot应用中实现了接口签名验证。这种机制可以有效防止请求被篡改和伪造,提高API的安全性。希望本文的示例代码和解释能够帮助你更好地理解和应用接口签名验证。