SpringBoot3實戰(zhàn)教程之實現(xiàn)接口簽名驗證功能
有時候我們要把自己的服務(wù)暴露給第三方去調(diào)用,為了防止接口不被授權(quán)訪問,我們一般采用接口簽名的方式去保護接口。
接下來松哥和大家聊一聊這個話題。
一 場景分析
什么時候需要接口簽名?
接口簽名是一種重要的安全機制,用于確保 API 請求的真實性、數(shù)據(jù)的完整性以及防止重放攻擊。
當(dāng)我們需要保護 API 接口不被未授權(quán)訪問、確保傳輸數(shù)據(jù)在過程中不被篡改,或者需要防止惡意用戶利用 API 進行攻擊時,就需要使用接口簽名。
松哥來舉幾個需要做接口簽名的例子:
- 開放 API 給第三方使用:當(dāng)你的 API 需要對外開放,讓第三方應(yīng)用或服務(wù)調(diào)用時,接口簽名可以驗證請求方的身份,確保只有擁有有效簽名的請求才能被接受。
- 數(shù)據(jù)完整性校驗:在數(shù)據(jù)傳輸過程中,接口簽名可以確保數(shù)據(jù)不被篡改。通過將請求數(shù)據(jù)與密鑰一起進行哈希運算,生成簽名值,接收方收到數(shù)據(jù)后可以用相同的方法生成簽名值進行對比,如果一致則數(shù)據(jù)未被篡改。
- 防止重放攻擊:通過在簽名中加入時間戳或隨機數(shù)等動態(tài)元素,接口簽名可以防止攻擊者截獲并重復(fù)發(fā)送有效的 API 請求。
- 接口防刷:為了防止接口被惡意調(diào)用,通常會采用一些防刷策略,比如限制請求頻率、使用驗證碼等。接口簽名可以作為防刷策略的一部分,確保請求的合法性。
- 敏感操作驗證:對于涉及敏感數(shù)據(jù)或重要操作的 API,如支付、轉(zhuǎn)賬等,接口簽名提供了額外的安全保障,確保請求的安全性。
- API 安全合規(guī):在某些行業(yè),如金融、醫(yī)療等,法律法規(guī)可能要求對 API 進行嚴格的安全控制,接口簽名是滿足這些合規(guī)要求的一種方式。
這里有一個很重要的點,就是我們的接口是暴露給對方服務(wù)端調(diào)用的,而不是暴露給前端調(diào)用的,這樣的場景需要做接口簽名。
二 簽名步驟
一般來說,接口簽名的步驟是這樣的:
- 構(gòu)造待簽名字符串:將請求方法、請求 URI、請求參數(shù)(包括查詢參數(shù)和請求體中的參數(shù))、時間戳等關(guān)鍵信息按照一定的規(guī)則拼接成待簽名字符串。
- 生成簽名:使用客戶端持有的私鑰(或密鑰)對待簽名字符串進行加密(或哈希運算),生成簽名。
- 發(fā)送請求:將生成的簽名作為請求的一部分(如請求頭)發(fā)送給服務(wù)器。
- 驗證簽名:服務(wù)器收到請求后,使用相同的規(guī)則構(gòu)造待簽名字符串,并使用對應(yīng)的公鑰(或密鑰)進行驗證。如果簽名驗證通過,則處理請求;否則,拒絕請求。
實現(xiàn)接口簽名時,需要注意密鑰管理、時間戳檢查、錯誤處理和日志記錄等安全實踐。
三 代碼實踐
接下來,基于 SpringBoot3,松哥來給大家演示一個接口簽名案例。
首先我們需要一個簽名和驗簽的工具類。這里我們采用 HmacSHA1 算法。
HmacSHA1 是一種基于 SHA-1 哈希算法的加密哈希消息認證碼(Hash-based Message Authentication Code,簡稱 HMAC)算法。HMAC 是一種用于驗證數(shù)據(jù)完整性和認證消息發(fā)送者身份的機制。它結(jié)合了加密哈希函數(shù)和加密密鑰,從而提供了一種安全的方式來確認數(shù)據(jù)的完整性和真實性。
public class SignUtils { /** * 使用 HmacSHA1 算法進行簽名 * @param secretKey 密鑰 * @param data 數(shù)據(jù) * @return */ public static String signWithHmacSha1(String secretKey, String data) { try { SecretKeySpec signingKey = new SecretKeySpec(secretKey.getBytes("UTF-8"), "HmacSHA1"); Mac mac = Mac.getInstance("HmacSHA1"); mac.init(signingKey); return Base64.getEncoder().encodeToString(mac.doFinal(data.getBytes("UTF-8"))); } catch (NoSuchAlgorithmException | InvalidKeyException | UnsupportedEncodingException e) { e.printStackTrace(); } return null; } /** * 驗證簽名 * @param secretKey 密鑰 * @param data 數(shù)據(jù) * @param hmac 簽名 * @return */ public static boolean verify(String secretKey, String data, String hmac) { String calculatedHmac = signWithHmacSha1(secretKey, data); return calculatedHmac.equals(hmac); } }
這個類很簡單,一個用來生成簽名的方法,這個方法按理說可以封裝到 SDK 中給到調(diào)用者,或者告訴調(diào)用者思路,由調(diào)用者自行實現(xiàn)。
第二個方法則是一個簽名驗證的方法,對用戶傳來的簽名信息進行校驗。
接下來我需要一個 App 信息的查詢類:
@Service public class AppService { private static final Map<String, String> APP_INFO = Map.of("app1", "sign1", "app2", "sign2"); public String getAppKey(String appId) { return APP_INFO.get(appId); } }
這個類的作用是這樣的:比如我們想要接入微信公眾號后臺,我們需要先在微信公眾號后臺配置我們自己的應(yīng)用信息,配置完成后,微信公眾號會給我們一個 appId 和 appSecret,微信自己會把這兩個信息存入到數(shù)據(jù)庫中,將來用戶請求來的時候,用戶會攜帶上 appId,但是不會攜帶 appSecret,微信公眾號可以根據(jù)用戶攜帶的 appId 去數(shù)據(jù)庫中查詢到 appSecret,然后進行驗簽。
松哥這個案例簡化了,直接模擬了兩個 appId 和 appSecret 存入到 Map 中,這里提供一個根據(jù) appId 查詢 appSecret 的函數(shù)。
接下來我們定義一個攔截器,在攔截器中對簽名進行驗證:
public class SignInterceptor implements HandlerInterceptor { public final static Logger logger = LoggerFactory.getLogger(SignInterceptor.class); AppService appService; public SignInterceptor(AppService appService) { this.appService = appService; } @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String appId = request.getHeader("appId"); String timestamp = request.getHeader("timestamp"); String sign = request.getHeader("sign"); if (StringUtils.hasText(appId) && StringUtils.hasText(timestamp) && StringUtils.hasText(sign)) { if (LocalDateTime.now().compareTo(LocalDateTime.ofInstant(Instant.ofEpochMilli(Long.parseLong(timestamp)), ZoneId.systemDefault()).plusMinutes(1L)) < 0) { String originalSign = appId + "-" + appService.getAppKey(appId) + "-" + timestamp; if (SignUtils.verify(appService.getAppKey(appId), originalSign, sign)) { return true; } else { logger.error("簽名驗證失敗"); } } else { logger.error("簽名已過期"); } } else { logger.error("簽名信息不完整"); } response.setStatus(401); return false; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { HandlerInterceptor.super.postHandle(request, response, handler, modelAndView); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { HandlerInterceptor.super.afterCompletion(request, response, handler, ex); } }
請求頭中主要有三個信息:
- 應(yīng)用 id
- 時間戳
- 生成的簽名
我們先從請求頭中取出來這三個信息,檢查是否為空;
然后判斷一下這個時間戳,要求必須是 1 分鐘之內(nèi)的請求,這個判斷目的主要是為了防止重放攻擊。
接下來,根據(jù) appId,以及根據(jù) appId 查詢出來的 appSecret,以及 timestamp,組成一個字符串,調(diào)用驗簽方法進行驗證,如果驗證通過,就說明請求沒問題。
最后我們配置一下,讓這個攔截器生效:
@Configuration public class WebConfig implements WebMvcConfigurer { @Autowired AppService appService; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new SignInterceptor(appService)) //只攔截需要接口驗簽的請求 .addPathPatterns("/app/**"); } }
OK,大功告成,接下來就可以寫接口進行測試了:
@RestController public class UserController { @GetMapping("/hello") public String hello() { return "hello"; } }
至于調(diào)用方要如何生成簽名呢?松哥也給大家一個例子:
@Autowired AppService appService; @Test void contextLoads() { String appId = "app1"; long timeMillis = System.currentTimeMillis(); String appSecret = appService.getAppKey(appId); String sign = SignUtils.signWithHmacSha1(appSecret, appId + "-" + appSecret + "-" + timeMillis); System.out.println("timeMillis = " + timeMillis); System.out.println("sign = " + sign); }
appId 和 appSecret 則是對方從我們這里申請得到的。
postman 上測試時,類似這樣:
到此這篇關(guān)于SpringBoot3實戰(zhàn):實現(xiàn)接口簽名驗證的文章就介紹到這了,更多相關(guān)SpringBoot接口簽名驗證內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
eclipse創(chuàng)建一個基于maven的web項目詳細步驟
開始學(xué)習(xí)maven,并用maven創(chuàng)建了第一個屬于自己的web項目,下面這篇文章主要給大家介紹了關(guān)于eclipse創(chuàng)建一個基于maven的web項目的相關(guān)資料,文中通過圖文介紹的非常詳細,需要的朋友可以參考下2023-12-12SpringCloud?eureka(server)微服務(wù)集群搭建過程
這篇文章主要介紹了微服務(wù)SpringCloud-eureka(server)集群搭建,?項目搭建的主要步驟和配置就是創(chuàng)建項目和引入pom依賴,本文通過圖文示例代碼相結(jié)合給大家介紹的非常詳細,需要的朋友可以參考下2022-07-07如何擴展Spring Cache實現(xiàn)支持多級緩存
這篇文章主要介紹了如何擴展Spring Cache實現(xiàn)支持多級緩存,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-11-11java聯(lián)系人管理系統(tǒng)簡單設(shè)計
這篇文章主要為大家詳細介紹了java聯(lián)系人管理系統(tǒng)簡單設(shè)計,具有一定的參考價值,感興趣的小伙伴們可以參考一下2016-10-10利用Spring Session和redis對Session進行共享詳解
這篇文章主要給大家介紹了關(guān)于利用Spring、Session和redis對Session進行共享的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。2017-09-09