Skip to content

应用间通信安全方案——接口签名 ​

来源:语雀原文 | 整理日期:2026-09-22

引言 ​

在现代分布式系统中,各个微服务、应用程序间的通信频繁,如何确保通信的安全性和数据的完整性成为了一个重要问题。接口签名(API Signature)是一种常见的解决方案,它通过对请求内容进行签名,确保请求数据在传输过程中未被篡改,并且能够验证请求的来源身份。

本文将通过一个真实案例,详细讲解接口签名的实现过程,包括其基本原理、实现步骤以及如何在Java+Redis环境下进行安全保护。

1. 什么是接口签名? ​

接口签名通常是在API请求的过程中,生成一个基于请求参数的签名,并将这个签名作为请求的一部分发送给服务器。服务器收到请求后,使用相同的签名算法对请求参数进行签名,并与请求中的签名进行比对,确保请求数据的完整性和来源的合法性。

2. 接口签名的基本原理 ​

接口签名的基本原理如下:

  • 客户端:

  • 通过私钥或共享密钥生成请求签名。

  • 将签名附加到HTTP请求的头部或请求体中。

  • 服务器端:

  • 服务器接收到请求后,使用相同的密钥对请求进行签名。

  • 比对客户端请求的签名和服务器生成的签名,如果一致,则请求有效;否则,说明请求被篡改或者是伪造的。

3. 接口签名的常见算法 ​

常见的签名算法包括:

  • HMAC(Hash-based Message Authentication Code): 使用密钥和哈希算法生成签名,是最常用的接口签名算法。
  • SHA(Secure Hash Algorithm): 通过哈希算法生成唯一的签名,常用于不可逆的签名操作。

4. 接口签名的具体实现 ​

4.1 生成签名的流程 ​

我们假设客户端和服务器之间使用HMAC-SHA256算法来生成签名,客户端发送一个请求,包括请求的URL、参数、时间戳等。服务器会校验这些内容的签名。

4.2 客户端生成签名(Java实现) ​

客户端在发送请求之前,需要按照一定规则生成签名。以下是一个简单的示例:

java
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.TreeMap;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

public class ClientSimulator {

    public static String generateSignature(Map<String, String> params, String secretKey) throws Exception {
        Map<String, String> sortedParams = new TreeMap<>(params); // 参数按键排序
        StringBuilder baseString = new StringBuilder();

        for (Map.Entry<String, String> entry : sortedParams.entrySet()) {
            baseString.append(entry.getKey()).append("=").append(entry.getValue()).append("&");
        }
        baseString.deleteCharAt(baseString.length() - 1); // 去掉末尾的 "&"

        // 使用 HMAC-SHA256 生成签名
        Mac mac = Mac.getInstance("HmacSHA256");
        SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
        mac.init(secretKeySpec);
        byte[] hash = mac.doFinal(baseString.toString().getBytes(StandardCharsets.UTF_8));

        // 转换为十六进制字符串
        StringBuilder signature = new StringBuilder();
        for (byte b : hash) {
            signature.append(String.format("%02x", b));
        }
        return signature.toString();
    }

    public static Map<String, String> createRequestParams() {
        return Map.of(
            "username", "testUser",
            "timestamp", String.valueOf(System.currentTimeMillis()),
            "data", "someImportantData"
        );
    }
}

4.3 服务器端验证签名(Java实现) ​

服务器在收到请求后,需要验证签名是否有效。服务器端的验证逻辑如下:

java
import redis.clients.jedis.Jedis;
import java.util.Map;

public class ServerSimulator {
    private final Jedis redis;
    private final String secretKey;

    public ServerSimulator(Jedis redis, String secretKey) {
        this.redis = redis;
        this.secretKey = secretKey;
    }

    public boolean validateRequest(Map<String, String> params, String clientSignature) throws Exception {
        // 生成服务器端的签名
        String serverSignature = ClientSimulator.generateSignature(params, secretKey);

        // 验证签名是否匹配
        if (!serverSignature.equals(clientSignature)) {
            System.out.println("签名验证失败!");
            return false;
        }

        // 验证请求是否为重放
        String timestamp = params.get("timestamp");
        String signatureKey = "signature:" + clientSignature + ":" + timestamp;

        if (redis.exists(signatureKey)) {
            System.out.println("请求重复,已拒绝!");
            return false;
        }

        // 存储签名到 Redis 并设置有效期(例如1分钟)
        redis.setex(signatureKey, 60, "1");
        System.out.println("签名验证通过,请求成功!");
        return true;
    }
}

4.4 主方法(完整流程测试) ​

通过 main 方法模拟客户端请求和服务端验证的全过程:

java
import redis.clients.jedis.Jedis;
import java.util.HashMap;
import java.util.Map;

public class Main {
    public static void main(String[] args) throws Exception {
        // Redis 客户端连接
        Jedis jedis = new Jedis("localhost", 6379);
        String secretKey = "mySecretKey"; // 秘钥

        // 模拟客户端请求
        Map<String, String> requestParams = new HashMap<>(ClientSimulator.createRequestParams());
        String signature = ClientSimulator.generateSignature(requestParams, secretKey);
        System.out.println("客户端生成的签名:" + signature);

        // 模拟服务端验证
        ServerSimulator server = new ServerSimulator(jedis, secretKey);

        // 第一次请求(正常情况)
        boolean result1 = server.validateRequest(requestParams, signature);
        System.out.println("第一次请求验证结果:" + result1);

        // 第二次请求(重放攻击)
        boolean result2 = server.validateRequest(requestParams, signature);
        System.out.println("第二次请求验证结果:" + result2);

        // 模拟新请求(时间戳不同)
        requestParams.put("timestamp", String.valueOf(System.currentTimeMillis()));
        String newSignature = ClientSimulator.generateSignature(requestParams, secretKey);
        boolean result3 = server.validateRequest(requestParams, newSignature);
        System.out.println("新请求验证结果:" + result3);

        // 关闭 Redis 连接
        jedis.close();
    }
}

5. 总结 ​

接口签名是一种保证应用间通信安全的重要技术手段。通过生成签名并验证签名的完整性,可以有效防止数据篡改和伪造请求。结合Redis等工具进行防重放攻击的措施,能够进一步提升系统的安全性。在实际的开发中,需要根据具体的业务需求来选择合适的签名算法和安全方案。