手把手实现 API 签名验证:客户端与服务端一致性关键点剖析 1. 完整的签名验证流程A. 核心原理客户端签名 = SHA256(参数 + 请求体 + 头信息 + appSecret) 服务器签名 = SHA256(参数 + 请求体 + 头信息 + appSecret) 如果两者相等 → 验证通过 否则 → 验证失败B. 详细步骤图解2. 关键:确保构建完全相同的字符串A. 客户端构建签名// 客户端签名生成代码publicclassClientSignatureGenerator{privateStringappId="app_001";privateStringappSecret="secret_key_123";// 只有客户端和服务端知道publicStringgenerateSignature(MapString,Stringparams,Stringbody,longtimestamp,Stringnonce){// 1. 按照特定顺序构建字符串StringstringToSign=buildStringToSign(params,body,timestamp,nonce);// 2. 加上 appSecretStringstringToSignWithSecret=stringToSign+appSecret;// 3. 计算 SHA256returnDigestUtils.sha256Hex(stringToSignWithSecret);}privateStringbuildStringToSign(MapString,Stringparams,Stringbody,longtimestamp,Stringnonce){// 关键:必须与服务端完全相同的规则// A. 处理查询参数(按字典序排序)SortedMapString,StringsortedParams=newTreeMap(params);StringparamString=sortedParams.entrySet().stream().map(entry-entry.getKey()+"="+entry.getValue()).collect(Collectors.joining(""));// B. 请求体(原样)StringbodyString=(body==null)?"":body;// C. 签名头参数(按固定顺序)StringheaderString=String.format("appId=%stimestamp=%dnonce=%s",appId,timestamp,nonce);// 拼接顺序必须固定!returnparamString+bodyString+headerString;}}B. 服务端验证签名// 服务端验证代码(你的代码解析)publicbooleanverifySignature(ApiSignaturesignature,HttpServletRequestrequest,StringappSecret){// 1. 获取客户端签名StringclientSignature=request.getHeader(signature.sign());// 2. 构建服务端签名字符串StringserverSignatureString=buildSignatureString(signature,request,appSecret);// 3. 计算服务端签名StringserverSignature=DigestUtil.sha256Hex(serverSignatureString);// 4. 比较returnObjects.equals(clientSignature,serverSignature);}privateStringbuildSignatureString(ApiSignaturesignature,HttpServletRequestrequest,StringappSecret){// 这里的关键是:构建方式必须与客户端完全一致!// A. 获取请求参数(与客户端相同处理)SortedMapString,StringparameterMap=getRequestParameterMap(request);// B. 获取请求体(与客户端相同处理)StringrequestBody=StrUtil.nullToDefault(ServletUtils.getBody(request),"");// C. 获取签名头参数(与客户端相同处理)SortedMapString,StringheaderMap=getRequestHeaderMap(signature,request);// D. 拼接字符串(顺序必须与客户端一致!)StringbaseString=MapUtil.join(parameterMap,"","=")+requestBody+MapUtil.join(headerMap,"","=");// E. 加上 appSecret(与客户端相同)returnbaseString+appSecret;}3. 为什么 timestamp 可以参与签名验证?A. timestamp 的双重作用// timestamp 在验证过程中的两个作用:// 1. 时间窗口验证(防重放)privatebooleanverifyTimestamp