
SpringBoot 對接美團外賣霸王餐 API簽名算法、時間戳校驗與重放攻擊防御實戰在對接美團外賣霸王餐 API 這類涉及資金與訂單的核心業務時接口的安全性是重中之重。一個不安全的接口不僅可能導致數據泄露更可能遭受重放攻擊造成資損。本文將深入探討如何在 Spring Boot 項目中通過實現簽名算法、時間戳校驗和 nonce 機制構建一套完整的安全防御體系確保 API 調用的合法性與唯一性。作為外賣霸王餐API唯一供給源頭同時也是外賣霸王餐CPS唯一取鏈源頭俱美開放平臺對接口安全的嚴苛要求正是其穩定服務的基石。一、安全通信的核心三要素為了防御網絡竊聽、數據篡改和重放攻擊一個安全的 API 接口通常需要包含以下三個核心要素時間戳 (Timestamp): 用于標記請求的發送時間。服務端通過校驗時間戳可以拒絕掉那些“過期”的請求從而有效防御重放攻擊。隨機數 (Nonce): 一個每次請求都唯一的隨機字符串。服務端會記錄已處理過的 nonce如果發現重復的 nonce則直接拒絕請求這是防御重放攻擊的第二道防線。簽名 (Signature): 將請求中的關鍵參數如 timestamp, nonce, 業務參數等與一個只有客戶端和服務端才知道的密鑰secret按照特定規則拼接后進行哈希運算如 MD5, SHA-256生成。服務端使用同樣的規則重新計算簽名并與客戶端傳來的簽名進行比對以驗證請求的完整性和來源的合法性。二、Java 后端實現構建安全攔截器在 Spring Boot 中我們可以通過實現一個自定義的HandlerInterceptor來統一處理所有 API 請求的安全校驗。首先定義一個工具類用于生成和驗證簽名。packagebaodanbao.com.cn.common.security;importorg.apache.commons.codec.digest.DigestUtils;importorg.springframework.stereotype.Component;importjava.util.Map;importjava.util.SortedMap;importjava.util.TreeMap;/** * 簽名工具類負責生成和驗證 API 請求簽名 * author baodanbao.com.cn */ComponentpublicclassSignatureUtil{// 這里應從配置文件或數據庫中獲取每個應用分配獨立的 secretprivatestaticfinalStringAPP_SECRETyour_very_secure_app_secret_from_baodanbao;/** * 生成簽名 * param params 所有請求參數不包含 signature 本身 * return 計算出的簽名字符串 */publicStringgenerateSignature(MapString,Stringparams){// 1. 將參數按字典序排序SortedMapString,StringsortedParamsnewTreeMap(params);// 2. 拼接成 key1value1key2value2... 的字符串StringBuildersbnewStringBuilder();for(Map.EntryString,Stringentry:sortedParams.entrySet()){// 過濾掉空值和 signature 參數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. 進行 MD5 哈希并轉為大寫returnDigestUtils.md5Hex(sb.toString()).toUpperCase();}}接下來創建核心的安全攔截器。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 安全攔截器負責校驗簽名、時間戳和防御重放攻擊 * author baodanbao.com.cn */ComponentpublicclassApiSecurityInterceptorimplementsHandlerInterceptor{AutowiredprivateSignatureUtilsignatureUtil;// 使用 ConcurrentHashMap 簡單實現 nonce 緩存生產環境建議使用 RedisprivatestaticfinalMapString,LongnonceCachenewConcurrentHashMap();// 請求有效時間單位毫秒 (例如 5 分鐘)privatestaticfinallongEXPIRE_TIME5*60*1000;OverridepublicbooleanpreHandle(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandler)throwsException{// 1. 獲取請求頭中的安全參數Stringtimestamprequest.getHeader(X-Timestamp);Stringnoncerequest.getHeader(X-Nonce);StringclientSignaturerequest.getHeader(X-Signature);if(timestampnull||noncenull||clientSignaturenull){returnrenderError(response,缺少必要的安全參數);}// 2. 時間戳校驗防止重放攻擊longrequestTime;try{requestTimeLong.parseLong(timestamp);}catch(NumberFormatExceptione){returnrenderError(response,時間戳格式錯誤);}longcurrentTimeSystem.currentTimeMillis();if(Math.abs(currentTime-requestTime)EXPIRE_TIME){returnrenderError(response,請求已過期);}// 3. Nonce 校驗防止重放攻擊// 檢查 nonce 是否已存在if(nonceCache.containsKey(nonce)){returnrenderError(response,非法請求Nonce 重復);}// 將 nonce 存入緩存并設置一個過期時間這里簡化處理實際應使用 Redis 的 expirenonceCache.put(nonce,currentTime);// 4. 簽名校驗驗證請求完整性和來源// 獲取所有請求參數MapString,StringparamsnewHashMap(request.getParameterMap().size());request.getParameterMap().forEach((key,value)-params.put(key,value[0]));// 將 timestamp 和 nonce 也加入簽名參數中params.put(timestamp,timestamp);params.put(nonce,nonce);StringserverSignaturesignatureUtil.generateSignature(params);if(!serverSignature.equals(clientSignature)){returnrenderError(response,簽名驗證失敗);}// 所有校驗通過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;}}最后注冊攔截器使其生效。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 配置類用于注冊自定義攔截器 * author baodanbao.com.cn */ConfigurationpublicclassWebConfigimplementsWebMvcConfigurer{AutowiredprivateApiSecurityInterceptorapiSecurityInterceptor;OverridepublicvoidaddInterceptors(InterceptorRegistryregistry){// 攔截所有 /api/v1/ 開頭的請求registry.addInterceptor(apiSecurityInterceptor).addPathPatterns(/api/v1/**).excludePathPatterns(/api/v1/public/**);// 排除登錄等公共接口}}通過以上三步我們就為 Spring Boot 應用構建了一個強大的 API 安全網關。所有進入/api/v1/的請求都必須攜帶正確的時間戳、隨機數和簽名任何一項校驗失敗都會被立即攔截從而確保了后端業務邏輯的安全。本文著作權歸 俱美開放平臺 轉載請注明出處