Java参数防篡改案例

wen java案例 2

本文目录导读:

Java参数防篡改案例

  1. 基础签名方案(HMAC-MD5)
  2. 完整的请求/响应拦截器实现
  3. 客户端调用示例
  4. 高级方案:基于RSA的非对称加密签名
  5. Spring Boot集成示例
  6. 更安全的实践建议
  7. 关键安全要点

在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);
            }
        }
    }
}

关键安全要点

  1. 密钥管理:密钥应存储在配置中心或密钥管理服务中,不要硬编码在代码里
  2. 时间戳验证:防止重放攻击,建议5分钟内有效
  3. 随机数验证:确保每个请求的nonce唯一且只使用一次
  4. 参数排序:签名前必须对参数进行字典序排序
  5. 常量时间比较:使用MessageDigest.isEqual防止时序攻击
  6. HTTPS使用:签名机制不能替代HTTPS,两者应配合使用
  7. 日志安全:不要在日志中记录签名参数和敏感信息

这个方案能有效防止参数被篡改,确保数据传输的完整性和真实性,根据实际业务需求,你可以选择合适的加密算法(如SHA256替代MD5)和签名方案。

抱歉,评论功能暂时关闭!