本文目录导读:

我来为你提供一个完整的Spring Boot接口签名实现案例。
项目结构
src/main/java/com/example/signature/
├── config/
│ └── SignConfig.java
├── interceptor/
│ └── SignInterceptor.java
├── annotation/
│ └── SignVerify.java
├── controller/
│ └── UserController.java
└── utils/
└── SignUtil.java
完整实现代码
1 签名工具类
package com.example.signature.utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.Arrays;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
public class SignUtil {
private static final Logger logger = LoggerFactory.getLogger(SignUtil.class);
/**
* 生成签名
* @param params 请求参数(不包含sign)
* @param secret 密钥
* @return 签名
*/
public static String generateSign(Map<String, String> params, String secret) {
// 移除sign和空值参数
Map<String, String> sortedParams = new TreeMap<>();
if (params != null) {
for (Map.Entry<String, String> entry : params.entrySet()) {
if (!"sign".equals(entry.getKey()) && StringUtils.hasText(entry.getValue())) {
sortedParams.put(entry.getKey(), entry.getValue());
}
}
}
// 拼接参数
StringBuilder sb = new StringBuilder();
for (Map.Entry<String, String> entry : sortedParams.entrySet()) {
sb.append(entry.getKey()).append("=").append(entry.getValue()).append("&");
}
// 拼接密钥
String stringToSign = sb.append("key=").append(secret).toString();
logger.debug("待签名字符串: {}", stringToSign);
return md5(stringToSign);
}
/**
* MD5加密
*/
public static String md5(String data) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] digest = md.digest(data.getBytes(StandardCharsets.UTF_8));
return bytesToHex(digest);
} catch (Exception e) {
logger.error("MD5加密失败", e);
return null;
}
}
/**
* HMAC-SHA256签名
*/
public static String hmacSha256(String data, String secret) {
try {
Mac mac = Mac.getInstance("HmacSHA256");
SecretKeySpec secretKeySpec = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
mac.init(secretKeySpec);
byte[] digest = mac.doFinal(data.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(digest);
} catch (Exception e) {
logger.error("HMAC-SHA256签名失败", e);
return null;
}
}
/**
* 字节数组转十六进制
*/
private static String bytesToHex(byte[] bytes) {
StringBuilder hexString = new StringBuilder();
for (byte b : bytes) {
String hex = Integer.toHexString(0xff & b);
if (hex.length() == 1) {
hexString.append('0');
}
hexString.append(hex);
}
return hexString.toString().toUpperCase();
}
/**
* 生成随机签名
*/
public static String generateRandomSign(String appId, String timestamp, String nonce, String secret) {
Map<String, String> params = new HashMap<>();
params.put("appId", appId);
params.put("timestamp", timestamp);
params.put("nonce", nonce);
return generateSign(params, secret);
}
/**
* 验证非空
*/
public static boolean checkNonNull(Map<String, String> params) {
if (params == null || params.isEmpty()) {
return false;
}
for (String value : params.values()) {
if (!StringUtils.hasText(value)) {
return false;
}
}
return true;
}
}
2 自定义注解
package com.example.signature.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 签名验证注解
*/
@Target({ElementType.METHOD, ElementType.TYPE}) // 方法或类上使用
@Retention(RetentionPolicy.RUNTIME)
public @interface SignVerify {
/**
* 是否需要验证
*/
boolean required() default true;
/**
* 超时时间(毫秒)
*/
long timeout() default 300000; // 默认5分钟
}
3 拦截器
package com.example.signature.interceptor;
import com.alibaba.fastjson.JSON;
import com.example.signature.annotation.SignVerify;
import com.example.signature.utils.SignUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
@Component
public class SignInterceptor implements HandlerInterceptor {
private static final Logger logger = LoggerFactory.getLogger(SignInterceptor.class);
// 密钥管理(实际项目中应该从配置中心或数据库中获取)
private static final Map<String, String> CLIENT_SECRETS = new HashMap<>();
static {
// 演示用的客户端密钥
CLIENT_SECRETS.put("test_app", "test_secret_key_123456");
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 如果不是控制器方法,直接放行
if (!(handler instanceof HandlerMethod)) {
return true;
}
HandlerMethod handlerMethod = (HandlerMethod) handler;
// 获取注解
SignVerify methodSign = handlerMethod.getMethodAnnotation(SignVerify.class);
SignVerify classSign = handlerMethod.getBeanType().getAnnotation(SignVerify.class);
// 如果没有注解,直接放行
if (methodSign == null && classSign == null) {
return true;
}
// 判断是否需要验证
SignVerify signVerify = methodSign != null ? methodSign : classSign;
if (!signVerify.required()) {
return true;
}
// 获取请求参数
Map<String, String> params = getParams(request);
// 验证参数
if (!verifyParams(params)) {
return responseError(response, "请求参数不完整");
}
// 验证时间戳
String timestamp = params.get("timestamp");
if (!verifyTimestamp(timestamp, signVerify.timeout())) {
return responseError(response, "请求已过期");
}
// 获取签名
String clientSign = params.get("sign");
String appId = params.get("appId");
// 获取客户端密钥
String secret = CLIENT_SECRETS.get(appId);
if (secret == null) {
return responseError(response, "无效的appId");
}
// 验证签名
String serverSign = SignUtil.generateSign(params, secret);
logger.debug("客户端签名: {}, 服务端签名: {}", clientSign, serverSign);
if (!clientSign.toUpperCase().equals(serverSign)) {
return responseError(response, "签名验证失败");
}
return true;
}
/**
* 获取请求参数
*/
private Map<String, String> getParams(HttpServletRequest request) throws IOException {
Map<String, String> params = new HashMap<>();
// 获取GET参数
Map<String, String[]> parameterMap = request.getParameterMap();
for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
params.put(entry.getKey(), entry.getValue()[0]);
}
// 获取POST body参数
if ("POST".equalsIgnoreCase(request.getMethod())) {
BufferedReader reader = request.getReader();
if (reader != null) {
try {
String line;
StringBuilder body = new StringBuilder();
while ((line = reader.readLine()) != null) {
body.append(line);
}
if (body.length() > 0) {
// 解析body参数(这里简化处理,只处理JSON格式)
String bodyStr = body.toString();
if (bodyStr.startsWith("{")) {
com.alibaba.fastjson.JSONObject jsonObject = JSON.parseObject(bodyStr);
for (String key : jsonObject.keySet()) {
if (!params.containsKey(key)) {
params.put(key, jsonObject.getString(key));
}
}
}
}
} finally {
reader.close();
}
}
}
return params;
}
/**
* 验证参数完整性
*/
private boolean verifyParams(Map<String, String> params) {
return params.containsKey("appId")
&& params.containsKey("timestamp")
&& params.containsKey("nonce")
&& params.containsKey("sign");
}
/**
* 验证时间戳
*/
private boolean verifyTimestamp(String timestamp, long timeout) {
try {
long clientTime = Long.parseLong(timestamp);
long currentTime = System.currentTimeMillis();
return Math.abs(currentTime - clientTime) <= timeout;
} catch (NumberFormatException e) {
return false;
}
}
/**
* 返回错误信息
*/
private boolean responseError(HttpServletResponse response, String message) throws IOException {
response.setContentType("application/json;charset=UTF-8");
response.setStatus(401);
PrintWriter writer = response.getWriter();
writer.write("{\"code\":401, \"message\":\"" + message + "\"}");
writer.flush();
writer.close();
return false;
}
}
4 配置类
package com.example.signature.config;
import com.example.signature.interceptor.SignInterceptor;
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 SignConfig implements WebMvcConfigurer {
@Autowired
private SignInterceptor signInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(signInterceptor)
.addPathPatterns("/api/**") // 添加需要拦截的路径
.excludePathPatterns("/api/public/**"); // 排除不需要拦截的路径
}
}
5 控制器示例
package com.example.signature.controller;
import com.example.signature.annotation.SignVerify;
import com.example.signature.utils.SignUtil;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
@RestController
@RequestMapping("/api")
@SignVerify // 类级别注解,所有接口都需要验证
public class UserController {
/**
* 需要签名验证的接口
*/
@PostMapping("/user/info")
public Map<String, Object> getUserInfo(@RequestBody Map<String, String> params) {
Map<String, Object> result = new HashMap<>();
result.put("code", 200);
result.put("message", "success");
result.put("data", "用户信息:" + params.get("userId"));
return result;
}
/**
* 不需要签名验证的接口(覆盖类级注解)
*/
@GetMapping("/public/config")
@SignVerify(required = false)
public Map<String, Object> getPublicConfig() {
Map<String, Object> result = new HashMap<>();
result.put("code", 200);
result.put("message", "success");
result.put("data", "公共配置");
return result;
}
/**
* 获取测试签名(用于演示)
*/
@GetMapping("/getTestSign")
public Map<String, String> getTestSign(
@RequestParam String appId,
@RequestParam String timestamp,
@RequestParam String nonce) {
String secret = "test_secret_key_123456";
Map<String, String> testParams = new HashMap<>();
testParams.put("appId", appId);
testParams.put("timestamp", timestamp);
testParams.put("nonce", nonce);
String sign = SignUtil.generateSign(testParams, secret);
Map<String, String> result = new HashMap<>();
result.put("sign", sign);
result.put("appId", appId);
result.put("timestamp", timestamp);
result.put("nonce", nonce);
return result;
}
}
6 客户端调用示例
package com.example.signature.client;
import org.springframework.web.client.RestTemplate;
import org.springframework.http.*;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
public class SignClient {
// 客户端信息
private static final String APP_ID = "test_app";
private static final String SECRET = "test_secret_key_123456";
private static final String BASE_URL = "http://localhost:8080/api";
public static void main(String[] args) {
// 构造请求参数
Map<String, String> params = new HashMap<>();
params.put("appId", APP_ID);
params.put("timestamp", String.valueOf(System.currentTimeMillis()));
params.put("nonce", UUID.randomUUID().toString().replace("-", ""));
params.put("userId", "123456");
// 生成签名
String sign = com.example.signature.utils.SignUtil.generateSign(params, SECRET);
params.put("sign", sign);
// 发送请求
RestTemplate restTemplate = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<Map<String, String>> entity = new HttpEntity<>(params, headers);
String url = BASE_URL + "/user/info";
ResponseEntity<String> response = restTemplate.exchange(
url, HttpMethod.POST, entity, String.class);
System.out.println("请求URL: " + url);
System.out.println("请求参数: " + params);
System.out.println("响应结果: " + response.getBody());
}
}
POM依赖
<dependencies>
<!-- Spring Boot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>2.0.25</version>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
签名流程说明
1 签名步骤
- 客户端准备参数:appId、timestamp、nonce、业务参数
- 生成待签名字符串:将参数按字母排序,用&拼接
- 生成签名:使用MD5或HMAC-SHA256算法
- 服务端验证:获取参数、验证时间戳、重新生成签名并对比
2 安全特性
- 时间戳验证:防止重放攻击
- 随机数nonce:保证每次请求唯一性
- 签名验证:防止参数篡改
- appId隔离:支持多客户端
3 扩展建议
- 支持更多签名算法(RSA、AES等)
- 添加nonce缓存,防止重复请求
- 集成Redis,实现令牌管理
- 支持接口限流
- 添加统一的错误码体系
这样,一个完整的Spring Boot接口签名案例就实现了,你可以根据实际需求进行调整和扩展。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播