SpringBoot攔截器實(shí)現(xiàn)API安全驗(yàn)證的示例詳解
前言
在開放平臺(tái)和第三方集成的項(xiàng)目中,如何確保 API 調(diào)用的安全性和可靠性是一個(gè)重要課題。特別是對(duì)于沒有用戶登錄場(chǎng)景的系統(tǒng)間調(diào)用,傳統(tǒng)的session或token認(rèn)證方式并不適用,數(shù)字簽名技術(shù)就成為了一種理想的選擇。
這種驗(yàn)證方式在開放平臺(tái)、SaaS服務(wù)、微服務(wù)間調(diào)用等場(chǎng)景下特別實(shí)用,能夠有效驗(yàn)證請(qǐng)求來源的合法性,防止參數(shù)被篡改和重放攻擊。
本文將詳細(xì)介紹如何基于 Spring Boot 攔截器和 HMAC-SHA256 算法,構(gòu)建一套輕量級(jí)但足夠安全的 API 驗(yàn)簽機(jī)制。
什么是數(shù)字簽名
數(shù)字簽名是一種用于驗(yàn)證數(shù)據(jù)完整性和真實(shí)性的技術(shù)手段。在 API 調(diào)用中,數(shù)字簽名通過以下方式保障安全:
- 1. 身份驗(yàn)證:確認(rèn)請(qǐng)求方身份的合法性
- 2. 數(shù)據(jù)完整性:確保請(qǐng)求參數(shù)在傳輸過程中未被篡改
- 3. 防止重放攻擊:通過時(shí)間戳等機(jī)制防止請(qǐng)求被重復(fù)使用
整體設(shè)計(jì)
設(shè)計(jì)思路
我們的驗(yàn)簽機(jī)制基于以下核心思想:
- 無侵入性:通過 Spring Boot 攔截器實(shí)現(xiàn),業(yè)務(wù)代碼無需改動(dòng)
- 算法安全性:采用業(yè)界成熟的 HMAC-SHA256 算法
- 配置靈活:支持多客戶端、多密鑰管理
架構(gòu)流程圖
客戶端請(qǐng)求 → 生成簽名 → 發(fā)送請(qǐng)求 → 攔截器驗(yàn)證 → 業(yè)務(wù)處理
↓ ↓ ↓ ↓ ↓
[參數(shù)整理] [HMAC加密] [攜帶簽名頭] [驗(yàn)簽邏輯] [通過/拒絕]
↓ ↓ ↓ ↓ ↓
參數(shù)排序 +時(shí)間戳加密 X-API-Key 密鑰匹配 正常響應(yīng)
↓ ↓ ↓ ↓ ↓
拼接參數(shù) Base64編碼 X-Timestamp 時(shí)間戳驗(yàn)證 或返回401
↓ ↓ ↓ ↓
待簽字符串 完整簽名 X-Signature 簽名對(duì)比
HMAC-SHA256 簽名原理
HMAC(Hash-based Message Authentication Code,基于哈希的消息認(rèn)證碼)結(jié)合了哈希函數(shù)和密鑰,提供了一種安全高效的消息認(rèn)證方式。
簽名生成算法
簽名 = Base64(HMAC-SHA256(時(shí)間戳 + 排序后的請(qǐng)求參數(shù), 密鑰))
算法步驟
1. 參數(shù)標(biāo)準(zhǔn)化:將所有請(qǐng)求參數(shù)按字典序排序
2. 數(shù)據(jù)拼接:將時(shí)間戳和排序后的參數(shù)按規(guī)則拼接
3. 簽名運(yùn)算:使用密鑰對(duì)拼接字符串進(jìn)行 HMAC-SHA256 運(yùn)輸
4. 編碼轉(zhuǎn)換:對(duì)加密結(jié)果進(jìn)行 Base64 編碼生成最終簽名
核心組件實(shí)現(xiàn)
1. 簽名工具類
簽名工具類是整個(gè)機(jī)制的核心,負(fù)責(zé)簽名的生成和驗(yàn)證邏輯
public class SignatureUtil {
/**
* 生成簽名
* 簽名算法:Base64(HMAC-SHA256(timestamp + sortedParams, secret))
*/
public static String generateSignature(Map<String, Object> params,
String timestamp, String secret) {
// 參數(shù)排序并拼接
String sortedParams = sortParams(params);
String dataToSign = timestamp + sortedParams;
// HMAC-SHA256加密并Base64編碼
HMac hmac = new HMac(HmacAlgorithm.HmacSHA256, secret.getBytes(StandardCharsets.UTF_8));
byte[] digest = hmac.digest(dataToSign);
return Base64.getEncoder().encodeToString(digest);
}
/**
* 驗(yàn)證時(shí)間戳有效性(防重放攻擊)
*/
public static boolean validateTimestamp(String timestamp, long tolerance) {
long requestTime = Long.parseLong(timestamp);
long currentTime = System.currentTimeMillis() / 1000;
return Math.abs(currentTime - requestTime) <= tolerance;
}
}
2. 攔截器
攔截器負(fù)責(zé)對(duì)所有受保護(hù)接口進(jìn)行統(tǒng)一的簽名驗(yàn)證
@Component
public class SignatureValidationInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
// 1. 獲取簽名頭信息
String timestamp = request.getHeader("X-Timestamp");
String signature = request.getHeader("X-Signature");
String apiKey = request.getHeader("X-Api-Key");
// 2. 驗(yàn)證必要參數(shù)
if (!StringUtils.hasText(timestamp) || !StringUtils.hasText(signature) || !StringUtils.hasText(apiKey)) {
return writeErrorResponse(response, "Missing required signature headers");
}
// 3. 驗(yàn)證時(shí)間戳(防重放攻擊)
if (!SignatureUtil.validateTimestamp(timestamp, timeTolerance)) {
return writeErrorResponse(response, "Invalid timestamp");
}
// 4. 獲取密鑰并驗(yàn)證簽名
String secret = securityProperties.getApiSecret(apiKey);
if (secret == null || !SignatureUtil.verifySignature(extractParams(request), timestamp, secret, signature)) {
return writeErrorResponse(response, "Invalid signature");
}
return true; // 驗(yàn)證通過,繼續(xù)處理請(qǐng)求
}
}
3. 配置類(WebMvcConfig)
配置類負(fù)責(zé)將攔截器集成到 Spring Boot 的請(qǐng)求處理鏈中:
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(signatureValidationInterceptor)
.addPathPatterns("/api/**") // 攔截所有API請(qǐng)求
.excludePathPatterns("/api/public/**"); // 排除公開接口
}
}
客戶端調(diào)用示例
請(qǐng)求頭設(shè)置
客戶端需要在請(qǐng)求頭中包含三個(gè)必要字段:
X-Api-Key:客戶端標(biāo)識(shí)X-Timestamp:當(dāng)前時(shí)間戳(秒級(jí))X-Signature:生成的簽名
完整調(diào)用流程
// 1. 準(zhǔn)備請(qǐng)求參數(shù)
Map<String, Object> params = new HashMap<>();
params.put("userId", "12345");
params.put("type", "profile");
// 2. 生成簽名
String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
String signature = SignatureUtil.generateSignature(params, timestamp, "your-secret");
// 3. 設(shè)置請(qǐng)求頭并發(fā)送請(qǐng)求
Headers headers = new Headers();
headers.set("X-Api-Key", "client1");
headers.set("X-Timestamp", timestamp);
headers.set("X-Signature", signature);
// GET /api/protected/data?userId=12345&type=profile
安全性設(shè)計(jì)要點(diǎn)
1. 防重放攻擊
- 時(shí)間戳驗(yàn)證:服務(wù)端驗(yàn)證時(shí)間戳的有效性(默認(rèn)容忍度5分鐘)
- 唯一性保證:相同參數(shù)在不同時(shí)間戳下生成不同簽名
- 配置靈活:可根據(jù)業(yè)務(wù)需求調(diào)整容忍時(shí)間
2. 密鑰管理
- 多客戶端支持:每個(gè)客戶端使用獨(dú)立的 API Key 和密鑰
- 配置化管理:通過配置文件或其他存儲(chǔ)組件統(tǒng)一管理密鑰映射
- 定期輪換:建議定期更換密鑰以提升安全性
3. 日志審計(jì)
- 請(qǐng)求日志:記錄驗(yàn)證失敗的關(guān)鍵信息(不包含完整簽名)
- IP追蹤:記錄客戶端真實(shí)IP地址
- 安全預(yù)警:異常簽名驗(yàn)證觸發(fā)告警機(jī)制
安全性增強(qiáng)建議
1. 傳輸層安全
- HTTPS強(qiáng)制:所有API請(qǐng)求必須通過HTTPS傳輸
- 證書驗(yàn)證:?jiǎn)⒂秒p向證書認(rèn)證增加安全性
- 協(xié)議升級(jí):及時(shí)更新SSL/TLS協(xié)議版本
2. 密鑰管理優(yōu)化
// 生成安全密鑰(32位隨機(jī)字符串)
public static String generateSecureKey() {
String chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
SecureRandom random = SecureRandom.getInstanceStrong();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 32; i++) {
sb.append(chars.charAt(random.nextInt(chars.length())));
}
return sb.toString();
}
3. 請(qǐng)求限流
建議結(jié)合 Redis 實(shí)現(xiàn)請(qǐng)求限流,防止破解:
// 簡(jiǎn)限流邏輯示例
String rateLimitKey = "api_limit:" + apiKey;
long count = redisTemplate.opsForValue().increment(rateLimitKey);
if (count > 100) { // 每分鐘限制100次請(qǐng)求
return writeErrorResponse(response, "Too many requests");
}
測(cè)試驗(yàn)證
正常流程測(cè)試
# 使用curl測(cè)試(需要先根據(jù)參數(shù)生成簽名) timestamp=$(date +%s) signature=$(生成簽名邏輯) curl -X GET "http://localhost:8080/api/protected/data?userId=12345" \ -H "X-Api-Key: client1" \ -H "X-Timestamp: $timestamp" \ -H "X-Signature: $signature"
異常場(chǎng)景測(cè)試
- 簽名錯(cuò)誤:修改參數(shù)但不更新簽名
- 時(shí)間戳過期:使用過期的時(shí)間戳
- 密鑰錯(cuò)誤:使用錯(cuò)誤的API Key
- 缺少頭信息:缺少必要的請(qǐng)求頭
總結(jié)
通過 Spring Boot 攔截器和 HMAC-SHA256 算法,我們實(shí)現(xiàn)了一套完整且實(shí)用的 API 簽名驗(yàn)證方案。這套機(jī)制有效解決了系統(tǒng)間調(diào)用的安全問題,而且對(duì)現(xiàn)有代碼幾乎零侵入,直接復(fù)用即可。
在實(shí)際項(xiàng)目中,你可以根據(jù)具體需求調(diào)整時(shí)間戳容忍度、密鑰管理策略等配置,實(shí)現(xiàn)靈活的安全控制。
倉(cāng)庫(kù)地址:https://github.com/yuboon/java-examples/tree/master/springboot-api-signature
到此這篇關(guān)于SpringBoot攔截器實(shí)現(xiàn)API安全驗(yàn)證的示例詳解的文章就介紹到這了,更多相關(guān)SpringBoot API 驗(yàn)簽內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java字符串相關(guān)類StringBuffer的用法詳解
java.lang包下的StringBuffer類,代表著可變的字符序列,可以用來對(duì)字符串內(nèi)容進(jìn)行增刪改操作。本文將通過示例詳細(xì)說說它的用法,感興趣的可以跟隨小編一起學(xué)習(xí)一下2022-10-10
Java String字符串和Unicode字符相互轉(zhuǎn)換代碼
這篇文章主要介紹了Java String字符串和Unicode字符相互轉(zhuǎn)換代碼,需要的朋友可以參考下2014-10-10
java接入創(chuàng)藍(lán)253短信驗(yàn)證碼的實(shí)例講解
下面小編就為大家分享一篇java接入創(chuàng)藍(lán)253短信驗(yàn)證碼的實(shí)例講解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2018-01-01
springboot+redis過期事件監(jiān)聽實(shí)現(xiàn)過程解析
這篇文章主要介紹了springboot+redis過期事件監(jiān)聽實(shí)現(xiàn)過程解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-03-03
SpringBoot參數(shù)校驗(yàn)之@Validated的使用詳解
這篇文章主要通過示例為大家詳細(xì)介紹一下介紹了SpringBoot參數(shù)校驗(yàn)中@Validated的使用方法,文中的示例代碼講解詳細(xì),需要的可以參考一下2022-06-06
springboot-jta-atomikos多數(shù)據(jù)源事務(wù)管理實(shí)現(xiàn)
本文主要介紹了springboot-jta-atomikos多數(shù)據(jù)源事務(wù)管理實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03

