本文目录导读:

我来详细介绍Java中实现接口签名的几种常见方法。
什么是接口签名
接口签名是通过对请求参数进行加密处理,生成一段校验码,用于验证请求的合法性和完整性,防止数据被篡改。
基本实现思路
// 签名生成流程 1. 收集所有请求参数 2. 按参数名排序 3. 拼接成字符串 4. 加上密钥 5. 进行加密(MD5/SHA等) 6. 生成签名
完整实现示例
1 工具类实现
import java.security.MessageDigest;
import java.util.*;
public class SignUtil {
/**
* 生成签名
* @param params 请求参数Map
* @param secret 密钥
* @return 签名串
*/
public static String generateSign(Map<String, String> params, String secret) {
// 1. 移除签名本身(防止重复计算)
params.remove("sign");
// 2. 获取所有参数名并排序
List<String> keys = new ArrayList<>(params.keySet());
Collections.sort(keys);
// 3. 拼接参数
StringBuilder sb = new StringBuilder();
for (String key : keys) {
String value = params.get(key);
if (value != null && !value.isEmpty()) {
sb.append(key).append("=").append(value).append("&");
}
}
// 4. 添加密钥
sb.append("key=").append(secret);
// 5. MD5加密
return md5(sb.toString()).toUpperCase();
}
/**
* 验证签名
* @param params 请求参数
* @param secret 密钥
* @return 是否验证通过
*/
public static boolean verifySign(Map<String, String> params, String secret) {
String sign = params.get("sign");
if (sign == null || sign.isEmpty()) {
return false;
}
String calSign = generateSign(params, secret);
return calSign.equals(sign);
}
/**
* MD5加密
*/
private static String md5(String input) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] digest = md.digest(input.getBytes("UTF-8"));
StringBuilder sb = new StringBuilder();
for (byte b : digest) {
sb.append(String.format("%02x", b & 0xff));
}
return sb.toString();
} catch (Exception e) {
throw new RuntimeException("MD5加密失败", e);
}
}
}
2 使用示例
public class SignDemo {
public static void main(String[] args) {
// 1. 请求参数
Map<String, String> params = new HashMap<>();
params.put("appId", "10001");
params.put("timestamp", "1695102400");
params.put("nonce", "random123");
params.put("data", "{\"name\":\"张三\",\"age\":25}");
// 2. 密钥(实际应用中应该从配置文件或密钥服务器获取)
String secret = "your-secret-key-123456";
// 3. 生成签名
String sign = SignUtil.generateSign(params, secret);
params.put("sign", sign);
System.out.println("签名结果: " + sign);
// 4. 验证签名
boolean isValid = SignUtil.verifySign(params, secret);
System.out.println("签名验证: " + (isValid ? "通过" : "失败"));
}
}
Spring Boot集成实现
1 自定义注解
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface VerifySign {
// 是否需要验证签名
boolean required() default true;
}
2 拦截器实现
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.*;
public class SignInterceptor implements HandlerInterceptor {
private final String secret;
public SignInterceptor(String secret) {
this.secret = secret;
}
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
// 获取请求参数
Map<String, String> params = getParams(request);
// 验证签名
if (!SignUtil.verifySign(params, secret)) {
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write("{\"code\":401,\"msg\":\"签名验证失败\"}");
return false;
}
return true;
}
private Map<String, String> getParams(HttpServletRequest request) {
Map<String, String> params = new HashMap<>();
Enumeration<String> names = request.getParameterNames();
while (names.hasMoreElements()) {
String name = names.nextElement();
params.put(name, request.getParameter(name));
}
return params;
}
}
3 配置拦截器
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 {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new SignInterceptor("your-secret-key"))
.addPathPatterns("/api/**")
.excludePathPatterns("/api/public/**");
}
}
Controller层使用
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api")
public class UserController {
@PostMapping("/user/create")
@VerifySign
public Result createUser(@RequestParam Map<String, String> params) {
// 业务逻辑
return Result.success();
}
}
增强功能实现
1 防重放攻击
public class AdvancedSignUtil extends SignUtil {
private static final long TIMEOUT = 5 * 60 * 1000; // 5分钟超时
public static boolean verifySignWithNonce(Map<String, String> params, String secret) {
// 1. 验证签名
if (!verifySign(params, secret)) {
return false;
}
// 2. 验证时间戳
String timestamp = params.get("timestamp");
if (timestamp == null) {
return false;
}
long time = Long.parseLong(timestamp);
if (Math.abs(System.currentTimeMillis() - time * 1000) > TIMEOUT) {
return false; // 请求超时
}
// 3. 验证nonce(防止重放)
String nonce = params.get("nonce");
if (nonce == null || isNonceUsed(nonce)) {
return false;
}
saveNonce(nonce);
return true;
}
// 实际应用中应该使用Redis存储
private static Set<String> usedNonces = new HashSet<>();
private static boolean isNonceUsed(String nonce) {
return usedNonces.contains(nonce);
}
private static void saveNonce(String nonce) {
usedNonces.add(nonce);
}
}
2 客户端示例
public class ApiClient {
private String appId;
private String secret;
public ApiClient(String appId, String secret) {
this.appId = appId;
this.secret = secret;
}
public Map<String, String> buildRequest(Map<String, String> data) {
Map<String, String> params = new HashMap<>();
params.put("appId", appId);
params.put("timestamp", String.valueOf(System.currentTimeMillis() / 1000));
params.put("nonce", UUID.randomUUID().toString().replace("-", ""));
params.put("data", JSON.toJSONString(data));
// 生成签名
String sign = SignUtil.generateSign(params, secret);
params.put("sign", sign);
return params;
}
}
最佳实践建议
- 使用HTTPS:确保传输层安全
- 密钥管理:密钥不要硬编码,使用配置中心
- 时间戳验证:防止重放攻击
- 参数排序:保证签名一致性
- 包含敏感数据:签名应包含所有参数
- 错误处理:提供清晰的错误信息
这就是Java接口签名的完整实现方案,可以根据实际需求选择合适的实现方式。