Spring Boot使用HMAC-SHA256对访问密钥加解密

 更新时间:2024年12月26日 08:34:05   作者:愤怒的代码  
本文主要介绍了使用HMAC-SHA256算法进行客户端和服务端之间的签名验签,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

一、客户端示例

假设这是一个Java客户端(可能是后端服务或桌面应用等),要调用你的服务接口 /api/secure,并用 HMAC-SHA256 做签名。

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Base64;

public class HmacClientExample {

    public static void main(String[] args) throws Exception {
        // 1) 准备必要参数
        String accessKeyId = "myKeyId";
        String accessKeySecret = "myKeySecret"; // 保密
        String method = "POST";
        String path = "/api/secure";
        // 例如携带一个 timestamp (yyyyMMddHHmmss)
        String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));

        // 2) 如果有请求体,需要计算 bodyHash (这里只是示例)
        //   实际可对 JSON 字符串做 MD5 或 SHA256,再 Hex 或 Base64
        String requestBody = "{\"foo\":\"bar\"}"; // JSON
        String bodyHash = sha256Hex(requestBody);

        // 3) 拼装 StringToSign (示例逻辑,可自定义)
        //    这里用换行分隔 method, path, timestamp, bodyHash
        String stringToSign = method + "\n" 
                              + path + "\n" 
                              + timestamp + "\n" 
                              + bodyHash;

        // 4) 做 HMAC-SHA256
        String signature = hmacSha256Base64(stringToSign, accessKeySecret);

        // 5) 将签名和 keyId、timestamp 放到 HTTP 头部
        //    伪代码: 构建 HTTP 请求
        System.out.println("X-AccessKeyId: " + accessKeyId);
        System.out.println("X-Timestamp: " + timestamp);
        System.out.println("X-Signature: " + signature);
        // 之后再把 requestBody 当作 JSON 发出 (POST)
        // ...

        // 这是示例演示,真实项目中可用 HttpClient、OkHttp 等发请求
    }

    /**
     * 计算字符串的 SHA-256 再转 hex (可选:也可用 Base64)
     */
    private static String sha256Hex(String data) throws Exception {
        MessageDigest digest = MessageDigest.getInstance("SHA-256");
        byte[] bytes = digest.digest(data.getBytes(StandardCharsets.UTF_8));
        return bytesToHex(bytes);
    }

    /**
     * HMAC-SHA256 + Base64
     */
    private static String hmacSha256Base64(String data, String secret) throws Exception {
        Mac mac = Mac.getInstance("HmacSHA256");
        SecretKeySpec keySpec = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
        mac.init(keySpec);
        byte[] rawHmac = mac.doFinal(data.getBytes(StandardCharsets.UTF_8));
        return Base64.getEncoder().encodeToString(rawHmac);
    }

    private static String bytesToHex(byte[] bytes) {
        StringBuilder sb = new StringBuilder(bytes.length * 2);
        for (byte b : bytes) {
            sb.append(String.format("%02x", b));
        }
        return sb.toString();
    }
}
  • 核心StringToSign 拼装 + HMAC-SHA256 计算签名 + 在请求头中带上 accessKeyIdtimestampsignature
  • bodyHash 的计算方式可自行定义,也可以用 MD5、直接放明文 body 等。只要客户端和服务端保持一致即可。

二、服务端示例 (Spring Boot)

下面以 Spring Boot + Controller 为例,展示如何验证签名。主要逻辑:

  • 从 HTTP 头中取 X-AccessKeyIdX-TimestampX-Signature
  • 根据 accessKeyId 找到 secret;
  • 用相同的方式拼装 StringToSign
  • 做同样的 HMAC-SHA256 计算;
  • 比对与客户端传来的 signature 是否相同。

2.1 Controller 示例

@RestController
@RequestMapping("/api")
public class SecureApiController {

    // 示例:内存中保存 keyId -> keySecret 映射
    private Map<String, String> keyStore = new HashMap<>();

    public SecureApiController() {
        // 假设这里初始化了一个myKeyId -> myKeySecret
        keyStore.put("myKeyId", "myKeySecret");
    }

    @PostMapping("/secure")
    public ResponseEntity<?> secureEndpoint(
            HttpServletRequest request,
            @RequestBody(required=false) String body // raw JSON
    ) {
        try {
            // 1) 从header读取
            String accessKeyId = request.getHeader("X-AccessKeyId");
            String timestamp = request.getHeader("X-Timestamp");
            String clientSignature = request.getHeader("X-Signature");

            if (accessKeyId == null || timestamp == null || clientSignature == null) {
                return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Missing auth headers");
            }

            // 2) 查找keySecret
            String keySecret = keyStore.get(accessKeyId);
            if (keySecret == null) {
                return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Invalid accessKeyId");
            }

            // 3) 计算 bodyHash(可选)
            //    假设客户端用了 sha256Hex(body)
            String bodyHash = sha256Hex(body == null ? "" : body);

            // 4) 与客户端相同的拼接方式
            String method = request.getMethod(); // "POST"
            String path = request.getRequestURI(); // "/api/secure"
            // StringToSign
            String stringToSign = method + "\n" 
                                + path + "\n" 
                                + timestamp + "\n" 
                                + bodyHash;

            // 5) 服务端做 HMAC-SHA256
            String serverSignature = hmacSha256Base64(stringToSign, keySecret);

            // 6) 比对签名
            if (!serverSignature.equals(clientSignature)) {
                return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Signature mismatch");
            }

            // 7) 可选校验: timestamp 是否过期
            if (!checkTimestampValid(timestamp)) {
                return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Timestamp expired or invalid");
            }

            // 8) 一切正常
            return ResponseEntity.ok("Success! Request body was: " + body);

        } catch (Exception e) {
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Auth error: " + e.getMessage());
        }
    }

    // 计算 SHA256Hex
    private String sha256Hex(String data) throws Exception {
        MessageDigest md = MessageDigest.getInstance("SHA-256");
        byte[] digest = md.digest(data.getBytes(StandardCharsets.UTF_8));
        return bytesToHex(digest);
    }

    // HMAC-SHA256 + Base64
    private String hmacSha256Base64(String data, String secret) throws Exception {
        Mac mac = Mac.getInstance("HmacSHA256");
        SecretKeySpec keySpec = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
        mac.init(keySpec);
        byte[] rawHmac = mac.doFinal(data.getBytes(StandardCharsets.UTF_8));
        return Base64.getEncoder().encodeToString(rawHmac);
    }

    private String bytesToHex(byte[] bytes) {
        StringBuilder sb = new StringBuilder(bytes.length * 2);
        for (byte b : bytes) {
            sb.append(String.format("%02x", b));
        }
        return sb.toString();
    }

    // 时间戳校验 (±15分钟示例)
    private boolean checkTimestampValid(String timestampStr) {
        try {
            // 这里假设 timestampStr 是 yyyyMMddHHmmss
            DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
            LocalDateTime reqTime = LocalDateTime.parse(timestampStr, fmt);
            LocalDateTime now = LocalDateTime.now();
            return !reqTime.isBefore(now.minusMinutes(15)) && !reqTime.isAfter(now.plusMinutes(15));
        } catch (Exception e) {
            return false;
        }
    }
}
  • 注意:上面为了演示方便,用 @RequestBody(required=false) String body 直接拿到原始 JSON 字符串,再做 sha256Hex;如果是对象映射,你要注意读取流计算摘要的先后顺序。
  • 你也可以在 Filter 或 Interceptor 里做这个签名验签逻辑,避免在每个 Controller 里写。
  • timestamp 校验 + 可能的 nonce 防重放(可用 Redis 记录 5 分钟内出现过的 (accessKeyId,timestamp,nonce)),以更好地防御重复调用。

三、总结

  • 客户端
    • 准备 accessKeyIdaccessKeySecret
    • 拼出 StringToSign(通常包含 method、path、timestamp、bodyHash 等);
    • HMAC-SHA256( StringToSignaccessKeySecret ) → signature
    • 在 HTTP 请求头里带上 accessKeyIdsignaturetimestamp
    • 用 JSON 作为请求体时,别忘了和服务端在 bodyHash 算法上保持一致。
  • 服务端
    • 通过 accessKeyId 找到对应的 accessKeySecret
    • 按同样规则构造 StringToSign
    • 计算 HMAC-SHA256 并和客户端的 signature 对比;
    • 一致则通过,不一致则 401/403;
    • 可加时间戳nonce限流 等加强安全性。
  • 优点
    • 不需要公钥/私钥,也不需要RSA 加解密;
    • 计算速度快、实现相对简单;
    • 通用性强,许多云厂商、API网关都采用类似 HMAC 签名模式。
  • 注意
    • 一定要保护好 accessKeySecret,客户端泄露就会被冒用。
    • 使用 HTTPS 来保证传输安全,防止中间人截获签名或篡改。
    • 若对大文件、流式上传等,需要在数据处理上稍作适配(hash可能需分段计算)。

这套 HMAC-SHA256 签名鉴权就是在很多云服务(阿里云、AWS、腾讯云)都在用的模式。只要客户端与服务端约定好StringToSign 的拼装方式、accessKeyId → secret 映射、时间戳/nonce防重放,就能形成一套轻量、高效的对外接口鉴权机制。

到此这篇关于Spring Boot使用HMAC-SHA256对访问密钥加解密的文章就介绍到这了,更多相关SpringBoot 访问密钥加解密内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java实现银行存取款

    Java实现银行存取款

    这篇文章主要为大家详细介绍了Java实现银行存取款,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-12-12
  • Java基础详解之内存泄漏

    Java基础详解之内存泄漏

    这篇文章主要介绍了Java基础详解之内存泄漏,文中有非常详细的代码示例,对正在学习java的小伙伴们有很好地帮助,需要的朋友可以参考下
    2021-04-04
  • Java利用Spire.Doc for Java实现在Word中插入分页符与分节符

    Java利用Spire.Doc for Java实现在Word中插入分页符与分节符

    在使用 Java 进行 Word 文档自动化生成的过程中,对文档布局的精细控制往往是开发者面临的实际需求,本文将介绍如何利用 Spire.Doc for Java 库,通过 Java 代码在 Word 文档中插入分页符和分节符,感兴趣的小伙伴可以参考下
    2026-05-05
  • Java 8 Lambda 表达式比较器使用示例代码

    Java 8 Lambda 表达式比较器使用示例代码

    这篇文章主要介绍了Java 8 Lambda 表达式比较器使用示例代码,非常不错,具有一定的参考借鉴价值,需要的朋友可以参考下
    2019-08-08
  • java多线程编程之Synchronized块同步方法

    java多线程编程之Synchronized块同步方法

    这篇文章主要介绍了java多线程编程之Synchronized块同步方法,synchronized关键字又称同步锁,当方法执行完后,会自动释放锁锁,只有一个线程能进入此方法,看看下文中各种例子对synchronized的详细解释
    2015-12-12
  • Spring Boot接口设计防篡改、防重放攻击详解

    Spring Boot接口设计防篡改、防重放攻击详解

    这篇文章主要给大家介绍了关于Spring Boot接口设计防篡改、防重放攻击的相关资料,文中通过示例代码介绍的非常详细,对大家学习或者使用Spring Boot具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧
    2019-07-07
  • Java生成独一无二的工单号实例

    Java生成独一无二的工单号实例

    这篇文章主要介绍了Java生成独一无二的工单号实例,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-09-09
  • SpringBoot整合Springsecurity实现数据库登录及权限控制功能

    SpringBoot整合Springsecurity实现数据库登录及权限控制功能

    本教程详细介绍了如何使用SpringBoot整合SpringSecurity实现数据库登录和权限控制,本文分步骤结合实例代码给大家介绍的非常详细,感兴趣的朋友跟随小编一起看看吧
    2024-10-10
  • IDEA的.idea文件夹和iml文件使用方式

    IDEA的.idea文件夹和iml文件使用方式

    这篇文章主要介绍了IDEA的.idea文件夹和iml文件使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-12-12
  • SpringCloud中的@FeignClient注解使用详解

    SpringCloud中的@FeignClient注解使用详解

    在Spring Cloud中使用Feign进行服务间的调用时,通常会使用@FeignClient注解来标记Feign客户端接口,这篇文章给大家介绍SpringCloud中的@FeignClient注解使用详解,感兴趣的朋友一起看看吧
    2025-06-06

最新评论