跳到主要内容Java 调用 UniHttp 接口参数乱序导致百度 SN 校验失败解决方案 | 极客日志Javajava
Java 调用 UniHttp 接口参数乱序导致百度 SN 校验失败解决方案
综述由AI生成Java 调用 UniHttp 接口时,请求参数顺序变化会导致百度 SN 签名认证失败。问题根源在于框架内部处理改变了 LinkedHashMap 中的键值对顺序。解决方案包括:一、客户端加密时按实际请求参数顺序生成 SN;二、重写 HttpApiProcessor 的 postBeforeHttpRequest 方法动态调整 URL 参数;三、使用自定义注解配合处理器实现灵活的参数重排序规则。通过对比代码通用性与改造复杂度,开发者可根据场景选择合适方案,确保接口调用成功。
筑梦师9 浏览 一、场景重现
本节将详细介绍使用 UniHttp 模式下,接入第三方接口时遇到 SN 验签方式验证失败的问题。通过重现接口调用现场,分析发生问题的背景。
1、UniHttp 模式下 SN 接口定义
按照官方文档说明,在 UniHttp 中进行百度搜索接口的调用。定义第一版的请求接口如下:
package com.yelang.project.thridinterface;
import com.burukeyou.uniapi.http.annotation.HttpApi;
import com.burukeyou.uniapi.http.annotation.param.QueryPar;
import com.burukeyou.uniapi.http.annotation.request.GetHttpInterface;
import com.burukeyou.uniapi.http.core.response.HttpResponse;
@HttpApi
public interface BaiduGeoSearchWithSnService {
@GetHttpInterface(url="https://api.map.baidu.com/place/v2/search")
public HttpResponse<String> getSearch(@QueryPar("query") String query, @QueryPar("region") String region, @QueryPar("output") String output, @QueryPar("scope") String scope, @QueryPar("ret_coordtype") String ret_coordtype, @QueryPar("page_size") int pageSize, @QueryPar("page_num") int pageNum, @QueryPar("ak") String ak, @QueryPar("sn") String sn);
}
2、第一次正式调用
使用 Junit 进行测试,关键代码如下:
package com.yelang.project.unihttp;
import java.io.UnsupportedEncodingException;
import java.util.LinkedHashMap;
import java.util.Map;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import com.burukeyou.uniapi.http.core.response.HttpResponse;
import com.yelang.project.thridinterface.BaiduGeoSearchWithSnService;
import com.yelang.project.thridinterface.signature.BaiduSignature;
@SpringBootTest
@RunWith(SpringRunner.class)
public class BaiduGeoSearchWithSnServiceCase {
private static final String AK_VALUE = "yourak";
private static final String SK_VALUE = "yoursk";
@Autowired
private BaiduGeoSearchWithSnService bdGeoSearchWithSnService;
@Test
public void searchBySn() throws UnsupportedEncodingException {
Map<String, String> paramsMap = new LinkedHashMap<>();
String api_prefix = "/place/v2/search?";
String query = "湘菜";
String region = "158";
String output = "json";
String scope = "2";
String ret_coordtype = "WGS84";
int pageSize = 20;
int pageNum = 0;
paramsMap.put("query", query);
paramsMap.put("region", region);
paramsMap.put("output", output);
paramsMap.put("scope", scope);
paramsMap.put("ret_coordtype", ret_coordtype);
paramsMap.put("page_size", String.valueOf(pageSize));
paramsMap.put("page_num", String.valueOf(pageNum));
paramsMap.put("ak", AK_VALUE);
BaiduSignature signature = new BaiduSignature(AK_VALUE, SK_VALUE, paramsMap, api_prefix);
String sn = signature.getSnByMap();
System.out.println("sn===>" + sn);
HttpResponse<String> result = bdGeoSearchWithSnService.getSearch(query, region, output, scope, ret_coordtype, pageSize, pageNum, AK_VALUE, sn);
System.out.println(result.getBodyResult());
}
}
3、第一次遇到 211 APP SN 校验失败
运行后控制台报错:{"status":211,"message":"APP SN 校验失败"}。
| 错误码 | 描述 |
|---|
| 211 | APP SN 校验失败,当用户请求的 SN 和服务端计算出来的 SN 不相等的时候提示 |
出现 SN 校验失败的原因是服务端 SN 值和客户端计算的值不一致,可能由参数顺序或密钥变化引起。
二、问题查找及开源寻求解决方案
1、抽丝剥茧找到问题所在
通过 Debug 查看 HttpResponse 对象中的 HttpUrl 值,发现请求参数顺序发生了变化。
HttpUrl( url=https://api.map.baidu.com/place/v2/search?, path=, anchor=null, queryParam={output=json, query=湘菜, scope=2, page_num=0, ak=yourak, sn=d1abf338a49bd82fca6992f5d2b9f686, region=158, ret_coordtype=WGS84, page_size=20}, pathParam={} )
对比发现,经过 UniHttp 处理后,参数顺序与接口定义顺序不一致,导致服务端加密计算的 SN 与客户端不一致。
2、与作者在 Issues 的交流
打开开源项目 UniHttp 的 Issues 页面咨询作者。作者建议参考文档实现 HttpApiProcessor 并重写 postBeforeHttpRequest 方法,在里面调整参数顺序或加签。
3、开源项目交流有感
UniHttp 在项目服务接口接入中提供了良好的便利性。遇到问题及时反馈并得到作者迅速回复,有助于快速定位问题。
三、朝着正确的方向解决问题
1、加密顺序按实际请求参数求解
已知导致验证失败的原因是参数不一致。在客户端加密时,把参数顺序按照服务端的加密顺序处理。
@Test
public void searchBySn() throws UnsupportedEncodingException {
Map<String, String> paramsMap = new LinkedHashMap<>();
String api_prefix = "/place/v2/search?";
String output = "json";
String query = "湘菜";
String scope = "2";
int pageNum = 0;
String region = "158";
String ret_coordtype = "WGS84";
int pageSize = 20;
paramsMap.put("output", output);
paramsMap.put("query", query);
paramsMap.put("scope", scope);
paramsMap.put("page_num", String.valueOf(pageNum));
paramsMap.put("ak", AK_VALUE);
paramsMap.put("region", region);
paramsMap.put("ret_coordtype", ret_coordtype);
paramsMap.put("page_size", String.valueOf(pageSize));
BaiduSignature signature = new BaiduSignature(AK_VALUE, SK_VALUE, paramsMap, api_prefix);
String sn = signature.getSnByMap();
}
2、一种兼容 Get 请求参数动态调整的方法
重写 BaiduHttpApiProcessor 中的 postBeforeHttpRequest 方法。核心是重新拼接请求 URL。
@Override
public UniHttpRequest postBeforeHttpRequest(UniHttpRequest uniHttpRequest, HttpApiMethodInvocation<BaiduHttpApi> methodInvocation) {
Map<String, Object> queryParam = uniHttpRequest.getHttpUrl().getQueryParam();
Map<String, Object> paramsMap = this.reorderingQueryParamMap(snMapParams, queryParam);
String queryString = this.reorderingQueryParam(snMapParams, queryParam);
uniHttpRequest.getHttpUrl().setQueryParam(null);
String url = uniHttpRequest.getHttpUrl().getUrl() + uniHttpRequest.getHttpUrl().getPath() + "?" + queryString;
uniHttpRequest.getHttpUrl().setUrl(url);
uniHttpRequest.getHttpUrl().setPath("");
return uniHttpRequest;
}
注意:接口定义时不要多加 ? 号,否则会导致双问号报错。
3、使用注解来设置重排序规则
自定义注解类,增加 snMapParams 和 snMode 参数。
@Inherited
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@HttpApi(processor = BaiduHttpApiProcessor.class)
public @interface BaiduHttpApi {
String url() default "${unihttp.channel.baidu.url}";
String snMapParams() default "";
boolean snMode() default false;
}
protected Map<String, Object> reorderingQueryParamMap(String snMapParams, Map<String, Object> queryParam) {
Map<String, Object> paramsMap = null;
if (StringUtils.isNotEmpty(snMapParams)) {
paramsMap = new LinkedHashMap<>();
String[] snMap = snMapParams.split(",");
for (String paramKey : snMap) {
paramsMap.put(paramKey, queryParam.get(paramKey));
}
paramsMap.put("ak", queryParam.get("ak"));
paramsMap.put("sn", queryParam.get("sn"));
} else {
paramsMap = queryParam;
}
return paramsMap;
}
@BaiduHttpApi(snMode = true, snMapParams = "query,region,output,scope,ret_coordtype,page_size,page_num")
public interface BaiduGeoSearchSnProcessorService { }
4、重写请求的 QueryParam 重排序方法
结合注解方式对请求参数的重排序进行处理,通用性更强。
@Override
public UniHttpRequest postBeforeHttpRequest(UniHttpRequest uniHttpRequest, HttpApiMethodInvocation<BaiduHttpApi> methodInvocation) {
BaiduHttpApi apiAnnotation = methodInvocation.getProxyApiAnnotation();
String snMapParams = apiAnnotation.snMapParams();
boolean snMode = apiAnnotation.snMode();
if (snMode) {
Map<String, Object> queryParam = uniHttpRequest.getHttpUrl().getQueryParam();
Map<String, Object> paramsMap = this.reorderingQueryParamMap(snMapParams, queryParam);
uniHttpRequest.getHttpUrl().setQueryParam(paramsMap);
}
return uniHttpRequest;
}
5、三种方法的使用场景及对比
| 序号 | 处理方式 | 通用性 | 改造复杂度 | 缺点 |
|---|
| 1 | 客户端兼容 | 低 | 低 | 需要根据请求参数定制 |
| 2 | 兼容 Get 调参 | 中 | 中 | 只支持 Get 方法 |
| 3 | 重写 QueryParam | 高 | 高 | 代码稍微复杂一点,维护难度高 |
四、总结
本文详细探讨了 Java 调用 UniHttp 接口时,解决请求参数乱序导致百度 SN 签名认证失败的三种策略。通过具体代码示例和操作步骤,帮助开发者理解和应用这些方法,提高接口调用的成功率。
相关免费在线工具
- Keycode 信息
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
- Escape 与 Native 编解码
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
- JavaScript / HTML 格式化
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
- JavaScript 压缩与混淆
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online