接美團(tuán)外賣霸王餐 API:簽名算法、時(shí)間戳校驗(yàn)與重放攻擊防御實(shí)戰(zhàn))
SpringBoot 對(duì)接美團(tuán)外賣霸王餐 API簽名算法、時(shí)間戳校驗(yàn)與重放攻擊防御實(shí)戰(zhàn)在對(duì)接美團(tuán)外賣霸王餐 API 這類涉及資金與訂單的核心業(yè)務(wù)時(shí)接口的安全性是重中之重。一個(gè)不安全的接口不僅可能導(dǎo)致數(shù)據(jù)泄露更可能遭受重放攻擊造成資損。本文將深入探討如何在 Spring Boot 項(xiàng)目中通過實(shí)現(xiàn)簽名算法、時(shí)間戳校驗(yàn)和 nonce 機(jī)制構(gòu)建一套完整的安全防御體系確保 API 調(diào)用的合法性與唯一性。作為外賣霸王餐API唯一供給源頭同時(shí)也是外賣霸王餐CPS唯一取鏈源頭俱美開放平臺(tái)對(duì)接口安全的嚴(yán)苛要求正是其穩(wěn)定服務(wù)的基石。一、安全通信的核心三要素為了防御網(wǎng)絡(luò)竊聽、數(shù)據(jù)篡改和重放攻擊一個(gè)安全的 API 接口通常需要包含以下三個(gè)核心要素時(shí)間戳 (Timestamp): 用于標(biāo)記請(qǐng)求的發(fā)送時(shí)間。服務(wù)端通過校驗(yàn)時(shí)間戳可以拒絕掉那些“過期”的請(qǐng)求從而有效防御重放攻擊。隨機(jī)數(shù) (Nonce): 一個(gè)每次請(qǐng)求都唯一的隨機(jī)字符串。服務(wù)端會(huì)記錄已處理過的 nonce如果發(fā)現(xiàn)重復(fù)的 nonce則直接拒絕請(qǐng)求這是防御重放攻擊的第二道防線。簽名 (Signature): 將請(qǐng)求中的關(guān)鍵參數(shù)如 timestamp, nonce, 業(yè)務(wù)參數(shù)等與一個(gè)只有客戶端和服務(wù)端才知道的密鑰secret按照特定規(guī)則拼接后進(jìn)行哈希運(yùn)算如 MD5, SHA-256生成。服務(wù)端使用同樣的規(guī)則重新計(jì)算簽名并與客戶端傳來的簽名進(jìn)行比對(duì)以驗(yàn)證請(qǐng)求的完整性和來源的合法性。二、Java 后端實(shí)現(xiàn)構(gòu)建安全攔截器在 Spring Boot 中我們可以通過實(shí)現(xiàn)一個(gè)自定義的HandlerInterceptor來統(tǒng)一處理所有 API 請(qǐng)求的安全校驗(yàn)。首先定義一個(gè)工具類用于生成和驗(yàn)證簽名。packagebaodanbao.com.cn.common.security;importorg.apache.commons.codec.digest.DigestUtils;importorg.springframework.stereotype.Component;importjava.util.Map;importjava.util.SortedMap;importjava.util.TreeMap;/** * 簽名工具類負(fù)責(zé)生成和驗(yàn)證 API 請(qǐng)求簽名 * author baodanbao.com.cn */ComponentpublicclassSignatureUtil{// 這里應(yīng)從配置文件或數(shù)據(jù)庫(kù)中獲取每個(gè)應(yīng)用分配獨(dú)立的 secretprivatestaticfinalStringAPP_SECRETyour_very_secure_app_secret_from_baodanbao;/** * 生成簽名 * param params 所有請(qǐng)求參數(shù)不包含 signature 本身 * return 計(jì)算出的簽名字符串 */publicStringgenerateSignature(MapString,Stringparams){// 1. 將參數(shù)按字典序排序SortedMapString,StringsortedParamsnewTreeMap(params);// 2. 拼接成 key1value1key2value2... 的字符串StringBuildersbnewStringBuilder();for(Map.EntryString,Stringentry:sortedParams.entrySet()){// 過濾掉空值和 signature 參數(shù)if(entry.getValue()!null!.equals(entry.getValue())!signature.equals(entry.getKey())){sb.append(entry.getKey()).append().append(entry.getValue());}}// 3. 在末尾拼接 secretsb.append(APP_SECRET);// 4. 進(jìn)行 MD5 哈希并轉(zhuǎn)為大寫returnDigestUtils.md5Hex(sb.toString()).toUpperCase();}}接下來創(chuàng)建核心的安全攔截器。packagebaodanbao.com.cn.common.security;importcom.fasterxml.jackson.databind.ObjectMapper;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.stereotype.Component;importorg.springframework.web.servlet.HandlerInterceptor;importjavax.servlet.http.HttpServletRequest;importjavax.servlet.http.HttpServletResponse;importjava.io.PrintWriter;importjava.util.HashMap;importjava.util.Map;importjava.util.concurrent.ConcurrentHashMap;/** * API 安全攔截器負(fù)責(zé)校驗(yàn)簽名、時(shí)間戳和防御重放攻擊 * author baodanbao.com.cn */ComponentpublicclassApiSecurityInterceptorimplementsHandlerInterceptor{AutowiredprivateSignatureUtilsignatureUtil;// 使用 ConcurrentHashMap 簡(jiǎn)單實(shí)現(xiàn) nonce 緩存生產(chǎn)環(huán)境建議使用 RedisprivatestaticfinalMapString,LongnonceCachenewConcurrentHashMap();// 請(qǐng)求有效時(shí)間單位毫秒 (例如 5 分鐘)privatestaticfinallongEXPIRE_TIME5*60*1000;OverridepublicbooleanpreHandle(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler)throwsException{// 1. 獲取請(qǐng)求頭中的安全參數(shù)Stringtimestamprequest.getHeader(X-Timestamp);Stringnoncerequest.getHeader(X-Nonce);StringclientSignaturerequest.getHeader(X-Signature);if(timestampnull||noncenull||clientSignaturenull){returnrenderError(response,缺少必要的安全參數(shù));}// 2. 時(shí)間戳校驗(yàn)防止重放攻擊longrequestTime;try{requestTimeLong.parseLong(timestamp);}catch(NumberFormatExceptione){returnrenderError(response,時(shí)間戳格式錯(cuò)誤);}longcurrentTimeSystem.currentTimeMillis();if(Math.abs(currentTime-requestTime)EXPIRE_TIME){returnrenderError(response,請(qǐng)求已過期);}// 3. Nonce 校驗(yàn)防止重放攻擊// 檢查 nonce 是否已存在if(nonceCache.containsKey(nonce)){returnrenderError(response,非法請(qǐng)求Nonce 重復(fù));}// 將 nonce 存入緩存并設(shè)置一個(gè)過期時(shí)間這里簡(jiǎn)化處理實(shí)際應(yīng)使用 Redis 的 expirenonceCache.put(nonce,currentTime);// 4. 簽名校驗(yàn)驗(yàn)證請(qǐng)求完整性和來源// 獲取所有請(qǐng)求參數(shù)MapString,StringparamsnewHashMap(request.getParameterMap().size());request.getParameterMap().forEach((key,value)-params.put(key,value[0]));// 將 timestamp 和 nonce 也加入簽名參數(shù)中params.put(timestamp,timestamp);params.put(nonce,nonce);StringserverSignaturesignatureUtil.generateSignature(params);if(!serverSignature.equals(clientSignature)){returnrenderError(response,簽名驗(yàn)證失敗);}// 所有校驗(yàn)通過returntrue;}privatebooleanrenderError(HttpServletResponseresponse,Stringmessage)throwsException{response.setCharacterEncoding(UTF-8);response.setContentType(application/json);PrintWriterwriterresponse.getWriter();MapString,ObjectresultnewHashMap();result.put(code,401);result.put(msg,message);writer.write(newObjectMapper().writeValueAsString(result));writer.flush();returnfalse;}}最后注冊(cè)攔截器使其生效。packagebaodanbao.com.cn.config;importbaodanbao.com.cn.common.security.ApiSecurityInterceptor;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.context.annotation.Configuration;importorg.springframework.web.servlet.config.annotation.InterceptorRegistry;importorg.springframework.web.servlet.config.annotation.WebMvcConfigurer;/** * Web MVC 配置類用于注冊(cè)自定義攔截器 * author baodanbao.com.cn */ConfigurationpublicclassWebConfigimplementsWebMvcConfigurer{AutowiredprivateApiSecurityInterceptorapiSecurityInterceptor;OverridepublicvoidaddInterceptors(InterceptorRegistryregistry){// 攔截所有 /api/v1/ 開頭的請(qǐng)求registry.addInterceptor(apiSecurityInterceptor).addPathPatterns(/api/v1/**).excludePathPatterns(/api/v1/public/**);// 排除登錄等公共接口}}通過以上三步我們就為 Spring Boot 應(yīng)用構(gòu)建了一個(gè)強(qiáng)大的 API 安全網(wǎng)關(guān)。所有進(jìn)入/api/v1/的請(qǐng)求都必須攜帶正確的時(shí)間戳、隨機(jī)數(shù)和簽名任何一項(xiàng)校驗(yàn)失敗都會(huì)被立即攔截從而確保了后端業(yè)務(wù)邏輯的安全。本文著作權(quán)歸 俱美開放平臺(tái) 轉(zhuǎn)載請(qǐng)注明出處