本文目录导读:

在Java Web开发中,参数防篡改是保障数据安全的重要环节,下面我提供几个完整的防篡改案例,从简单到复杂。
基础签名方案(HMAC-MD5)
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.Map;
import java.util.TreeMap;
import java.util.UUID;
/**
* 基于HMAC-MD5的参数防篡改工具类
*/
public class ParamSignatureUtil {
private static final String SECRET_KEY = "YourSecretKey123!"; // 实际应用中应该从配置中心获取
/**
* 生成请求签名
*/
public static String generateSignature(Map<String, String> params, String timestamp, String nonce) {
try {
// 1. 参数排序
TreeMap<String, String> sortedParams = new TreeMap<>(params);
StringBuilder sb = new StringBuilder();
// 2. 拼接参数(不包括sign本身)
for (Map.Entry<String, String> entry : sortedParams.entrySet()) {
if (entry.getValue() != null && !entry.getValue().isEmpty()) {
sb.append(entry.getKey()).append("=").append(entry.getValue()).append("&");
}
}
// 3. 添加时间戳和随机数
sb.append("timestamp=").append(timestamp).append("&");
sb.append("nonce=").append(nonce);
// 4. 拼接密钥并计算HMAC-MD5
String message = sb.toString();
Mac mac = Mac.getInstance("HmacMD5");
SecretKeySpec keySpec = new SecretKeySpec(SECRET_KEY.getBytes(StandardCharsets.UTF_8), "HmacMD5");
mac.init(keySpec);
byte[] result = mac.doFinal(message.getBytes(StandardCharsets.UTF_8));
// 5. 转换为十六进制字符串
return bytesToHex(result);
} catch (Exception e) {
throw new RuntimeException("生成签名失败", e);
}
}
/**
* 验证签名
*/
public static boolean verifySignature(Map<String, String> params,
String timestamp,
String nonce,
String clientSign) {
// 1. 检查时间戳有效性(防止重放攻击)
long ts = Long.parseLong(timestamp);
long currentTime = System.currentTimeMillis();
if (Math.abs(currentTime - ts) > 5 * 60 * 1000) { // 5分钟有效期
return false;
}
// 2. 生成服务端签名
String serverSign = generateSignature(params, timestamp, nonce);
// 3. 使用常量时间比较,防止时序攻击
return MessageDigest.isEqual(
serverSign.getBytes(StandardCharsets.UTF_8),
clientSign.getBytes(StandardCharsets.UTF_8)
);
}
private static String bytesToHex(byte[] bytes) {
StringBuilder result = new StringBuilder();
for (byte b : bytes) {
result.append(String.format("%02x", b));
}
return result.toString();
}
/**
* 生成随机数(防止重放攻击)
*/
public static String generateNonce() {
return UUID.randomUUID().toString().replace("-", "");
}
}
完整的请求/响应拦截器实现
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
/**
* 参数防篡改过滤器
*/
public class AntiTamperFilter implements Filter {
private static final String SECRET_KEY = "YourSecretKey123!";
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// 初始化配置
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
// 只对POST请求进行签名验证
if ("POST".equalsIgnoreCase(httpRequest.getMethod())) {
try {
// 1. 获取请求参数
Map<String, String> params = new HashMap<>();
Map<String, String[]> requestParams = httpRequest.getParameterMap();
for (Map.Entry<String, String[]> entry : requestParams.entrySet()) {
if (entry.getValue().length > 0) {
params.put(entry.getKey(), entry.getValue()[0]);
}
}
// 2. 获取签名相关参数
String sign = params.get("sign");
String timestamp = params.get("timestamp");
String nonce = params.get("nonce");
if (sign == null || timestamp == null || nonce == null) {
httpResponse.sendError(400, "Missing signature parameters");
return;
}
// 3. 移除签名相关参数
params.remove("sign");
params.remove("timestamp");
params.remove("nonce");
// 4. 验证签名
boolean isValid = ParamSignatureUtil.verifySignature(
params, timestamp, nonce, sign
);
if (!isValid) {
httpResponse.sendError(403, "Invalid signature");
return;
}
} catch (Exception e) {
httpResponse.sendError(500, "Signature verification error");
return;
}
}
// 验证通过,继续请求
chain.doFilter(request, response);
}
@Override
public void destroy() {
// 清理资源
}
}
客户端调用示例
import java.util.HashMap;
import java.util.Map;
/**
* 客户端请求示例
*/
public class ClientExample {
public static void main(String[] args) {
// 1. 准备业务参数
Map<String, String> params = new HashMap<>();
params.put("userId", "123456");
params.put("amount", "100.00");
params.put("account", "6222021234567890");
// 2. 生成时间戳和随机数
String timestamp = String.valueOf(System.currentTimeMillis());
String nonce = ParamSignatureUtil.generateNonce();
// 3. 生成签名
String sign = ParamSignatureUtil.generateSignature(params, timestamp, nonce);
// 4. 添加签名参数到请求中
params.put("timestamp", timestamp);
params.put("nonce", nonce);
params.put("sign", sign);
// 5. 发送请求(这里使用简单的HTTP客户端示例)
sendRequest(params);
}
private static void sendRequest(Map<String, String> params) {
// 实际开发中使用HttpClient或RestTemplate
// 这里仅作示例
System.out.println("请求参数: " + params);
}
}
高级方案:基于RSA的非对称加密签名
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Base64;
/**
* RSA签名方案(更安全,防篡改和防抵赖)
*/
public class RSASignatureUtil {
/**
* 生成密钥对
*/
public static KeyPair generateKeyPair() throws Exception {
KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
generator.initialize(2048);
return generator.generateKeyPair();
}
/**
* 保存密钥到文件
*/
public static void saveKeyPair(KeyPair keyPair, String privatePath, String publicPath) throws Exception {
// 保存私钥
byte[] privateBytes = keyPair.getPrivate().getEncoded();
Files.write(Paths.get(privatePath), Base64.getEncoder().encodeToString(privateBytes).getBytes());
// 保存公钥
byte[] publicBytes = keyPair.getPublic().getEncoded();
Files.write(Paths.get(publicPath), Base64.getEncoder().encodeToString(publicBytes).getBytes());
}
/**
* 加载私钥
*/
public static PrivateKey loadPrivateKey(String path) throws Exception {
byte[] keyBytes = Base64.getDecoder().decode(new String(Files.readAllBytes(Paths.get(path))).trim());
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);
java.security.KeyFactory factory = java.security.KeyFactory.getInstance("RSA");
return factory.generatePrivate(spec);
}
/**
* 加载公钥
*/
public static PublicKey loadPublicKey(String path) throws Exception {
byte[] keyBytes = Base64.getDecoder().decode(new String(Files.readAllBytes(Paths.get(path))).trim());
X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes);
java.security.KeyFactory factory = java.security.KeyFactory.getInstance("RSA");
return factory.generatePublic(spec);
}
/**
* 使用私钥签名
*/
public static String sign(String data, PrivateKey privateKey) throws Exception {
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initSign(privateKey);
signature.update(data.getBytes("UTF-8"));
byte[] signedBytes = signature.sign();
return Base64.getEncoder().encodeToString(signedBytes);
}
/**
* 使用公钥验签
*/
public static boolean verify(String data, String sign, PublicKey publicKey) throws Exception {
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initVerify(publicKey);
signature.update(data.getBytes("UTF-8"));
byte[] signedBytes = Base64.getDecoder().decode(sign);
return signature.verify(signedBytes);
}
}
Spring Boot集成示例
import org.springframework.web.bind.annotation.*;
import javax.validation.constraints.NotNull;
@RestController
@RequestMapping("/api")
public class TransferController {
/**
* 转账接口(带防篡改验证)
*/
@PostMapping("/transfer")
public ApiResponse<String> transfer(
@RequestParam @NotNull String fromAccount,
@RequestParam @NotNull String toAccount,
@RequestParam @NotNull String amount,
@RequestParam @NotNull String timestamp,
@RequestParam @NotNull String nonce,
@RequestParam @NotNull String sign) {
// 1. 构建参数Map(排除签名参数)
Map<String, String> params = new HashMap<>();
params.put("fromAccount", fromAccount);
params.put("toAccount", toAccount);
params.put("amount", amount);
// 2. 验证签名
boolean valid = ParamSignatureUtil.verifySignature(params, timestamp, nonce, sign);
if (!valid) {
return ApiResponse.error(403, "签名验证失败");
}
// 3. 业务处理
// BusinessService.transfer(fromAccount, toAccount, amount);
return ApiResponse.success("转账成功");
}
/**
* 获取数据接口(带签名校验)
*/
@GetMapping("/data")
public ApiResponse<String> getData(
@RequestParam String userId,
@RequestParam String sign) {
// 对GET请求的简单签名验证
Map<String, String> params = new HashMap<>();
params.put("userId", userId);
// 简单的MD5验证
String expectedSign = MD5Util.md5(SECRET_KEY + "userId=" + userId);
if (!expectedSign.equals(sign)) {
return ApiResponse.error(403, "签名验证失败");
}
return ApiResponse.success("数据获取成功");
}
}
更安全的实践建议
/**
* 高级安全建议实现
*/
public class SecurityBestPractices {
/**
* 防重放攻击的非ce校验
*/
@Component
public static class NonceManager {
private final Cache<String, Boolean> nonceCache =
CacheBuilder.newBuilder()
.expireAfterWrite(10, TimeUnit.MINUTES)
.maximumSize(10000)
.build();
public boolean checkNonce(String nonce) {
// 检查nonce是否使用过
if (nonceCache.getIfPresent(nonce) != null) {
return false; // 已经使用过
}
// 标记为已使用
nonceCache.put(nonce, true);
return true;
}
}
/**
* 参数完整性校验的数据注解
*/
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface IntegrityCheck {
String[] fields() default {}; // 需要校验的字段
boolean includeTimestamp() default true;
boolean includeNonce() default true;
}
/**
* 响应加密
*/
public static class ResponseEncryptor {
public static String encryptResponse(Object data, String secretKey) {
try {
// 使用AES加密响应数据
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
// ... 加密逻辑
return Base64.getEncoder().encodeToString("encrypted".getBytes());
} catch (Exception e) {
throw new RuntimeException("响应加密失败", e);
}
}
}
}
关键安全要点
- 密钥管理:密钥应存储在配置中心或密钥管理服务中,不要硬编码在代码里
- 时间戳验证:防止重放攻击,建议5分钟内有效
- 随机数验证:确保每个请求的nonce唯一且只使用一次
- 参数排序:签名前必须对参数进行字典序排序
- 常量时间比较:使用
MessageDigest.isEqual防止时序攻击 - HTTPS使用:签名机制不能替代HTTPS,两者应配合使用
- 日志安全:不要在日志中记录签名参数和敏感信息
这个方案能有效防止参数被篡改,确保数据传输的完整性和真实性,根据实际业务需求,你可以选择合适的加密算法(如SHA256替代MD5)和签名方案。
版权声明: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直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播