Spring?Boot使用HMAC-SHA256對(duì)訪問密鑰加解密
一、客戶端示例
假設(shè)這是一個(gè)Java客戶端(可能是后端服務(wù)或桌面應(yīng)用等),要調(diào)用你的服務(wù)接口 /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) 準(zhǔn)備必要參數(shù)
String accessKeyId = "myKeyId";
String accessKeySecret = "myKeySecret"; // 保密
String method = "POST";
String path = "/api/secure";
// 例如攜帶一個(gè) timestamp (yyyyMMddHHmmss)
String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
// 2) 如果有請(qǐng)求體,需要計(jì)算 bodyHash (這里只是示例)
// 實(shí)際可對(duì) 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 頭部
// 偽代碼: 構(gòu)建 HTTP 請(qǐng)求
System.out.println("X-AccessKeyId: " + accessKeyId);
System.out.println("X-Timestamp: " + timestamp);
System.out.println("X-Signature: " + signature);
// 之后再把 requestBody 當(dāng)作 JSON 發(fā)出 (POST)
// ...
// 這是示例演示,真實(shí)項(xiàng)目中可用 HttpClient、OkHttp 等發(fā)請(qǐng)求
}
/**
* 計(jì)算字符串的 SHA-256 再轉(zhuǎn) 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計(jì)算簽名 + 在請(qǐng)求頭中帶上accessKeyId、timestamp、signature。 - bodyHash 的計(jì)算方式可自行定義,也可以用 MD5、直接放明文 body 等。只要客戶端和服務(wù)端保持一致即可。
二、服務(wù)端示例 (Spring Boot)
下面以 Spring Boot + Controller 為例,展示如何驗(yàn)證簽名。主要邏輯:
- 從 HTTP 頭中取
X-AccessKeyId,X-Timestamp,X-Signature。 - 根據(jù)
accessKeyId找到 secret; - 用相同的方式拼裝
StringToSign; - 做同樣的 HMAC-SHA256 計(jì)算;
- 比對(duì)與客戶端傳來的
signature是否相同。
2.1 Controller 示例
@RestController
@RequestMapping("/api")
public class SecureApiController {
// 示例:內(nèi)存中保存 keyId -> keySecret 映射
private Map<String, String> keyStore = new HashMap<>();
public SecureApiController() {
// 假設(shè)這里初始化了一個(gè)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) 計(jì)算 bodyHash(可選)
// 假設(shè)客戶端用了 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) 服務(wù)端做 HMAC-SHA256
String serverSignature = hmacSha256Base64(stringToSign, keySecret);
// 6) 比對(duì)簽名
if (!serverSignature.equals(clientSignature)) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Signature mismatch");
}
// 7) 可選校驗(yàn): 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());
}
}
// 計(jì)算 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();
}
// 時(shí)間戳校驗(yàn) (±15分鐘示例)
private boolean checkTimestampValid(String timestampStr) {
try {
// 這里假設(shè) 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;如果是對(duì)象映射,你要注意讀取流和計(jì)算摘要的先后順序。 - 你也可以在 Filter 或 Interceptor 里做這個(gè)簽名驗(yàn)簽邏輯,避免在每個(gè) Controller 里寫。
timestamp校驗(yàn) + 可能的 nonce 防重放(可用 Redis 記錄 5 分鐘內(nèi)出現(xiàn)過的(accessKeyId,timestamp,nonce)),以更好地防御重復(fù)調(diào)用。
三、總結(jié)
- 客戶端
- 準(zhǔn)備
accessKeyId,accessKeySecret; - 拼出
StringToSign(通常包含 method、path、timestamp、bodyHash 等); - HMAC-SHA256(
StringToSign,accessKeySecret) →signature; - 在 HTTP 請(qǐng)求頭里帶上
accessKeyId,signature,timestamp; - 用 JSON 作為請(qǐng)求體時(shí),別忘了和服務(wù)端在 bodyHash 算法上保持一致。
- 準(zhǔn)備
- 服務(wù)端
- 通過
accessKeyId找到對(duì)應(yīng)的accessKeySecret; - 按同樣規(guī)則構(gòu)造
StringToSign; - 計(jì)算 HMAC-SHA256 并和客戶端的
signature對(duì)比; - 一致則通過,不一致則 401/403;
- 可加時(shí)間戳、nonce、限流 等加強(qiáng)安全性。
- 通過
- 優(yōu)點(diǎn)
- 不需要公鑰/私鑰,也不需要RSA 加解密;
- 計(jì)算速度快、實(shí)現(xiàn)相對(duì)簡(jiǎn)單;
- 通用性強(qiáng),許多云廠商、API網(wǎng)關(guān)都采用類似 HMAC 簽名模式。
- 注意
- 一定要保護(hù)好
accessKeySecret,客戶端泄露就會(huì)被冒用。 - 使用 HTTPS 來保證傳輸安全,防止中間人截獲簽名或篡改。
- 若對(duì)大文件、流式上傳等,需要在數(shù)據(jù)處理上稍作適配(hash可能需分段計(jì)算)。
- 一定要保護(hù)好
這套 HMAC-SHA256 簽名鑒權(quán)就是在很多云服務(wù)(阿里云、AWS、騰訊云)都在用的模式。只要客戶端與服務(wù)端約定好StringToSign 的拼裝方式、accessKeyId → secret 映射、時(shí)間戳/nonce防重放,就能形成一套輕量、高效的對(duì)外接口鑒權(quán)機(jī)制。
到此這篇關(guān)于Spring Boot使用HMAC-SHA256對(duì)訪問密鑰加解密的文章就介紹到這了,更多相關(guān)SpringBoot 訪問密鑰加解密內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
idea沒有services窗口、沒有springboot啟動(dòng)項(xiàng)問題
這篇文章主要介紹了idea沒有services窗口、沒有springboot啟動(dòng)項(xiàng)問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-05-05
SpringBoot?整合?Spring-Session?實(shí)現(xiàn)分布式會(huì)話項(xiàng)目實(shí)戰(zhàn)
本文主要介紹了SpringBoot?整合?Spring-Session?實(shí)現(xiàn)分布式會(huì)話項(xiàng)目實(shí)戰(zhàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-07-07
Spring?@Conditional通過條件控制bean注冊(cè)過程
這篇文章主要為大家介紹了Spring?@Conditional通過條件控制bean注冊(cè)過程詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-02-02
Springboot 全局時(shí)間格式化三種方式示例詳解
時(shí)間格式化在項(xiàng)目中使用頻率是非常高的,當(dāng)我們的 API? 接口返回結(jié)果,需要對(duì)其中某一個(gè) date? 字段屬性進(jìn)行特殊的格式化處理,通常會(huì)用到 SimpleDateFormat? 工具處理,這篇文章主要介紹了3 種 Springboot 全局時(shí)間格式化方式,需要的朋友可以參考下2024-01-01
Java Web Fragment在項(xiàng)目中使用方法詳解
這篇文章主要介紹了Web Fragment在項(xiàng)目中使用方法詳解,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-02-02

