Spring Boot使用HMAC-SHA256对访问密钥加解密
作者:愤怒的代码
本文主要介绍了使用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
计算签名 + 在请求头中带上accessKeyId
、timestamp
、signature
。 - bodyHash 的计算方式可自行定义,也可以用 MD5、直接放明文 body 等。只要客户端和服务端保持一致即可。
二、服务端示例 (Spring Boot)
下面以 Spring Boot + Controller 为例,展示如何验证签名。主要逻辑:
- 从 HTTP 头中取
X-AccessKeyId
,X-Timestamp
,X-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)
),以更好地防御重复调用。
三、总结
- 客户端
- 准备
accessKeyId
,accessKeySecret
; - 拼出
StringToSign
(通常包含 method、path、timestamp、bodyHash 等); - HMAC-SHA256(
StringToSign
,accessKeySecret
) →signature
; - 在 HTTP 请求头里带上
accessKeyId
,signature
,timestamp
; - 用 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 访问密钥加解密内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!