Java 调用高德地图Sig签名遇10007 INVALID_USER_SIGNATURE的解决之道

Java 调用高德地图Sig签名遇10007 INVALID_USER_SIGNATURE的解决之道

目录

前言

一、如何开启高德的数字签名

1、应用配置

2、官方的生成机制

二、Java集成UniHttp

1、UniHttp接口定义

2、非SIG验证访问

3、高德数字签名的实现

三、常见问题及解决办法

1、编程式参数顺序设置

2、参数重排序设置

3、特殊字符的处理

四、高德地图与百度地图数字签名对比

五、总结


前言

        在现代软件开发中,地图服务的集成已成为众多应用的必备功能之一。大家日常使用频率较高的除了百度地图之外,高德地图也是其中的重要服务提供商,其丰富的 API 接口为开发者提供了便利。然而,在使用 Java 调用高德地图服务时,如果开发者开启了数字签名的机制,可能会遇到各种问题,其中最常见的便是 10007 INVALID_USER_SIGNATURE 错误。这一错误表明数字签名未通过验证,导致无法正常访问高德地图的服务。

        在之前的博客中,我曾经对百度地图的SN数字签名机制进行了详细的讲解,包括基于Java的原生请求机制和基于UniHttp的封装请求机制,原文链接如下:

序号博客地址
1Java 开发者如何搞定百度地图 SN 权限签名实践-以搜索2.0接口为例
2Java调用UniHttp接口请求失败?一次开源的深入实践-百度SN签名认证场景下参数乱序问题的三种解决策略

        很多朋友反馈对百度地图的数字签名机制介绍很详细,能不能对高德地图的数字签名机制也进行介绍介绍。众所周知,数字签名机制是为了确保请求的安全性和合法性而设计的。当开发者在开放平台的控制台中开启了数字签名功能后,就需要按照指定的算法生成数字签名。如果签名生成过程中出现任何问题,如参数错误、算法不匹配或密钥错误等,都会导致请求错误,因此数字签名机制其实是一种安全保障机制。虽然之前对百度地图的数字签名机制有一定的了解,本来以为高德验证机制比较跟百度的类似,转换使用起来应该非常块,实践证明。相似性和直接照搬还是不同的,“纸上得来终觉浅,绝知此事要躬行”。两个开放平台之间还是有一定的差别的。

        本文即在此背景下产生,文章主要讲解如何在高德地图中开启SIG数字签名,开启数字签名后可能会遇到什么问题,然后介绍使用Java进行调用时可能遇到的场景以及如何解决调用时返回"info":"INVALID_USER_SIGNATURE","infocode":"10007"的错误。通过这些内容的展开,开发者可以有效地解决 Java 调用高德地图服务时遇到的 10007 INVALID_USER_SIGNATURE 错误,同时由于高德地图在官网上也没有提供示例代码,因此本文也算是一种补充,让各位开发者可以直接参考使用。

一、如何开启高德的数字签名

        本节将简单介绍如何在高德地图中开启数字签名的应用配置和官方的生成机制介绍。

1、应用配置

        进入控制台-应用管理-我的应用。

        1)新建Key/选择相应的Key。

        2)点击“设置”,打开数字签名,提交后关闭“设置”页面

        (注意:该私钥与Key对应,请注意保存,谨防泄露。)

2、官方的生成机制

        我们可以查看官方提供的数字签名生成机制说明网页链接,如下图所示:

        从官方的说明可以知晓:

生成签名

签名格式:sig=MD5(请求参数(包括key)键值对(按参数名的升序排序),加(请注意“加”字无需输入)私钥)。例如:

请求服务为“testservice”;请求参数分别为“a=23,b=12,d=48,f=8,c=67”;私钥为“bbbbb”。则数字签名为:sig=md5(a=23&b=12&c=67&d=48&f=8bbbbb)

注意:
生成签名的内容,(上文提到的拼装的参数,也就是md5()中的内容),必须为utf-8编码格式。在计算md5的参数如果出现+号,请正常计算sig,但在请求的时候,需要用urlencode进行编码再请求。请求参数排序需要注意,如果参数名的第一个字母顺序相同,就比较第二个字母。以此类推,直至得到排序结果。

在请求中添加签名

将签名sig作为参数添加至请求参数中:参数名为sig,值为根据请求参数与私钥计算出的值。

二、Java集成UniHttp

        本节将以UniHttp为例,详细讲解如何在Java中集成UniHttp,并且使用Java实现非Sig验证的接口访问以及如何定义高德数字签名工具类。通过本节可以了解如何在UniHttp中进行接口的定义以及高德数字签名的具体实现。

1、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(url = "https://restapi.amap.com/v3/geocode") public interface AmapGeocodeWithSigService { /** * - 带sig签名的地理编码API服务 * @param address address * @param city city * @param key key * @param sig 经过计算加密的sig * @return */ @GetHttpInterface("/geo") public HttpResponse<String> getGeoWithSig(@QueryPar("address") String address,@QueryPar("city") String city, @QueryPar("key") String key, @QueryPar("sig") String sig); }

2、非SIG验证访问

        为了让大家理解数字签名机制,这里我们简单介绍一下非SIG验证访问。即知道key就可以成功调用接口。简单方法如下:

package com.yelang.project.unihttp; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.util.LinkedHashMap; import org.junit.Test; import org.junit.runner.RunWith; 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.common.utils.StringUtils; import com.yelang.project.thridinterface.AmapGeocodeService; import com.yelang.project.thridinterface.AmapGeocodeWithSigService; import com.yelang.project.thridinterface.signature.AmapSignature; @SpringBootTest @RunWith(SpringRunner.class) public class AmapGeoSearchBySigCase { private static final String AMAP_CLIENT_NOSIG_AK = "youramapkey"; @Autowired private AmapGeocodeService geocodeService;//地理和逆地理编码 @Test public void geoWithNoSig() throws InterruptedException { String address = "湖南大学"; String city = "430100";//长沙 String key = AMAP_CLIENT_NOSIG_AK; //step1、调用高德地理编码接口将地址转为经纬度 HttpResponse<String> result = geocodeService.getGeo(address, city, key); if(StringUtils.isNotEmpty(result.getBodyResult())) { System.out.println(result.getBodyResult()); } } }

        在控制台中运行以上程序后,在控制台中可以看到以下输出,说明接口成功调用。

3、高德数字签名的实现

        根据官网文档里面介绍的数字签名的实现方式,我们新建一个高德地图的数字签名生成器,能够自动处理数据,并生成正确的数字签名。核心代码如下:

package com.yelang.project.thridinterface.signature; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; /** * - 高德 signature签名 * @author 夜郎king */ public class AmapSignature { /** * -应用申请的AK值 */ private String akValue; /** * - 应用申请的SK值 */ private String skValue; /** * - Map参数集合 */ private Map<String, ?> data; /** * - API接口访问前缀 */ private String apiPrefix; public String getAkValue() { return akValue; } public void setAkValue(String akValue) { this.akValue = akValue; } public String getSkValue() { return skValue; } public void setSkValue(String skValue) { this.skValue = skValue; } public Map<String, ?> getData() { return data; } public void setData(Map<String, ?> data) { this.data = data; } public String getApiPrefix() { return apiPrefix; } public void setApiPrefix(String apiPrefix) { this.apiPrefix = apiPrefix; } public AmapSignature() { super(); } public AmapSignature(String akValue, String skValue, Map<String, ?> data) { super(); this.akValue = akValue; this.skValue = skValue; this.data = data; } //来自stackoverflow的MD5计算方法,调用了MessageDigest库函数,并把byte数组结果转换成16进制 private String MD5(String md5) { try { java.security.MessageDigest md = java.security.MessageDigest.getInstance("MD5"); byte[] array = md.digest(md5.getBytes()); StringBuffer sb = new StringBuffer(); for (int i = 0; i < array.length; ++i) { sb.append(Integer.toHexString((array[i] & 0xFF) | 0x100).substring(1, 3)); } return sb.toString(); } catch (java.security.NoSuchAlgorithmException e) { } return null; } /** * 生成签名 * * @param reOrder 重排序 * @return 签名 */ public String generateSignature(boolean reOrder) { // 参数排序 List<String> keys = new ArrayList<>(this.data.keySet()); // 需要重排序 if (reOrder) { Collections.sort(keys); } // 拼接参数 StringBuilder sb = new StringBuilder(); for (String key : keys) { sb.append(key).append("=").append(this.data.get(key)).append("&"); } System.out.println("其它md5求解:" + this.MD5(sb.substring(0, sb.length() - 1) + this.skValue)); // 计算MD5 return MD5(sb.substring(0, sb.length() - 1) + this.skValue); } }

        重排序的设置非常重要,主要是为了解决参数乱序时导致的请求数字签名不一致的问题。在下文中会详细涉及。

三、常见问题及解决办法

        使用Java定义了高德地图的数字签名的具体实现后,下面我们就来使用三个真实的场景进行测试,看看会遇到什么问题以及我们是如何解决的。通过这几个问题的解决,就可以掌握在实际开发中容易遇到的问题。

1、编程式参数顺序设置

        其实不管是百度地图还是高德地图,他们的数字签名的认证设计思想都比较相同,尤其是加密时的参数顺序,一定是不能错的,一旦有的参数顺序不对,那么最终加密得到的结果已经是错的。因此保证请求参数顺序非常重要。保证参数顺序的方式有很多种,首先我们来讲解第一种方式编程式参数顺序设置,顾名思义就是使用代码的方式来指定参数顺序。在Java当中,我们可以使用LinkedHashMap或者TreeMap来进行参数的设置,都是可以的。只是使用TreeMap默认就是按照字符来进行排序的。两者相差不大,我们以地理编码接口为例:

@Test public void getWithSig() { String city = "430100";//长沙 String key = AMAP_CLIENT_SIG_AK; String address = "岳麓山风景区"; String secret_key = AMAP_CLIENT_SECRET_KEY; LinkedHashMap<String, String> data = new LinkedHashMap<String, String>(); data.put("address", address); data.put("city", city); data.put("key", key); System.out.println(data); AmapSignature amapSignature = new AmapSignature(key, secret_key, data); String sig = amapSignature.generateSignature(false); System.out.println("sig==" + sig); //step1、调用高德地理编码接口将地址转为经纬度 HttpResponse<String> result = geoCodeWithSigService.getGeoWithSig(address, city, key,sig); System.out.println("开始执行"); if(StringUtils.isNotEmpty(result.getBodyResult())) { System.out.println(result.getBodyResult()); } }

        这里的参数我们按照参数的key的值进行了手动排序,在程序中我们设置不需要参数排序,

String sig = amapSignature.generateSignature(false);

        来看一下程序的运行结果:

        我们随便改变参数的顺序,比如把key放到最前面去,在次运行结果如下:

2、参数重排序设置

        编程式参数设置严重的依赖开发工程师,而且极易被忘记,因此我们在生成sig时直接对参数进行重排序,然后按照重排序的方式进行处理即可。重排序的方法很简单,创建sig时传入计算参数即可:

String sig = amapSignature.generateSignature(true);

        传入参数后,计算的参数的函数逻辑如下:

        这里会对keys这个集合进行重排序,这样就得到了参数顺序统一化的处理。在此运行后,无论前面的集合使用什么类型,哪怕是HashMap都没有问题,因为在生成数字签名时进行了统一排序就保证了正确性。

3、特殊字符的处理

        在官网的手册上,有一个关于特殊字符的说明:

在计算md5的参数如果出现+号,请正常计算sig,但在请求的时候,需要用urlencode进行编码再请求。

        这里的+号只是一个符号,还有可能有其它的符号,可以看下在查询字符串中增加特殊符号后,会报什么错误?

        有了特殊字符后又会报这个10007的错误,在上面的提示中已经包含了解决方法,sig计算正常计算数字签名,但是请求时需要编码。编码的核心方法如下:

//step2、对包含特殊字符的进行值重编码 address = URLEncoder.encode(address, "UTF-8");

        其它没有特殊字符的值可以不做处理,再次运行后在控制台可以看到接口访问正常了。

四、高德地图与百度地图数字签名对比

        这里简单对两个平台的数字签名进行一个简单的对比。

百度地图高德地图
请求前缀是否带入计算
请求参数可否乱序
编码复杂度
是否包含示例

五、总结

        以上就是本文的主要内容,文章主要讲解如何在高德地图中开启SIG数字签名,开启数字签名后可能会遇到什么问题,然后介绍使用Java进行调用时可能遇到的场景以及如何解决调用时返回"info":"INVALID_USER_SIGNATURE","infocode":"10007"的错误。通过详细讲解如何在Java中集成UniHttp,集成UniHttp之后可能会碰到什么问题,最后通过问题的解决积累开发经验,同时对百度地图和高德地图的数字签名方式进行了简单的对比,为大家实际开发时做一个简单的参考。行文仓促,定有许多的不足之处,欢迎各位朋友在评论区批评指正,不胜感激。

Read more

【数据结构】栈与队列:数据结构中的双生子

【数据结构】栈与队列:数据结构中的双生子

栈与队列:数据结构中的双生子 ✨前言:在数据结构的学习中,栈(Stack) 与 队列(Queue) 是两种基础而强大的存在。它们看似简单,却在各种算法和系统设计中扮演着核心角色。理解它们的特性和实现原理,是每位程序员成长的必经之路。今天我将带大家深入学习栈和队列。 📖专栏:【数据结构】 目录 * 栈与队列:数据结构中的双生子 * 一、栈(Stack):后进先出的数据世界 * 1.1 栈的核心概念 * 1.2 栈的实现方式 * 二、队列(Queue):先进先出的公平机制 * 2.1 队列的核心概念 * 2.2 队列的实现方式 * 三、总结 一、栈(Stack):后进先出的数据世界 1.1 栈的核心概念 栈是一种特殊的线性表,

By Ne0inhk
【CVPR2025 DEIM】超详细!手把手训练自己的数据集教学:从源码下载,配置虚拟环境,准备数据集、训练、验证、推理测试 ,实现0到1的完整教学过程。本文在win系统上训练,最强实时目标检测算法!

【CVPR2025 DEIM】超详细!手把手训练自己的数据集教学:从源码下载,配置虚拟环境,准备数据集、训练、验证、推理测试 ,实现0到1的完整教学过程。本文在win系统上训练,最强实时目标检测算法!

🔥DEIM创新改进目录:全新DEIM有效涨点改进目录 | 包含各种最新顶会顶刊:卷积模块、注意力模块、特征融合模块、有效特征聚合提取模块,上采样模块、下采样模块,二次创新模块、独家创新,特殊场景检测等最全大论文及小论文必备创新改进点 🔥全新DEIM创新改进专栏地址:全网独家DEIM创新改进高效涨点+永久更新中(至少500+创新改进🗡剑指小论文、大论文)+小白也能简单高效跑实验+容易发各种级别小论文 本文目录 一、下载CVPR2025 DEIM官方源码  二、创新DEIM项目虚拟环境 第一步创建一个自己的虚拟环境: 第二步进入到自己的虚拟环境: 第三步:安装pytorch,建议不要安装太新版本 第四步:直接复制以下所有命令到控制台“终端里面粘贴回车运行” 三、准备自己的数据集和配置自己数据集步骤 3.1 本文以训练Visdrone2019无人机数据集为例 3.2 将自己数据集放到datasets文件夹里 3.3 配置数据步骤 四、使用DEIM训练自己的数据集 4.1

By Ne0inhk
set_map的实现+set/map加持秒杀高频算法题锻炼算法思维

set_map的实现+set/map加持秒杀高频算法题锻炼算法思维

🎬 胖咕噜的稞达鸭:个人主页 🔥 个人专栏: 《数据结构》《C++初阶高阶》《算法入门》 ⛺️技术的杠杆,撬动整个世界! 开始进入set和map的学习目录 * * * * set类的实现 * set的构造和迭代器: * set:erase和find * set:count * set:lower_bound和upper_bound * multiset和set * map:insert * map:operator[ ] * multimap和map的差异: * 力扣题目链接: * 两个数组的交集 * 环形链表 * 随机链表的复制 * 前k个高频单词 * set和map的构造对比: set类的实现 set的声明:T就是set底层的关键字的类型; set默认要求支持T比较,如果不支持或者想按照自己的需求走可以自行实现仿函数传给第二个模板参数。 set底层存储数据的内存是从空间配置器申请的,如果需要可以自己实现内存池,传给第三个参数。 set底层是红黑树实现,增删查效率是O(logN),迭代器遍历走

By Ne0inhk
【洛谷】从记忆化搜索到动态规划 状态表示 + 转移方程 + 空间优化全攻略

【洛谷】从记忆化搜索到动态规划 状态表示 + 转移方程 + 空间优化全攻略

文章目录 * 从记忆化搜索到动态规划 * 记忆化搜索 * 递归改递推 * 动态规划 * 下楼梯 * 数字三角形 小编提醒:在动态规划问题中,将数组命名为f和dp都可以。 从记忆化搜索到动态规划 记忆化搜索 在搜索的过程中,如果搜索树中有很多重复的结点,此时可以通过⼀个 “备忘录”,记录第⼀次搜索到 的结果。当下⼀次搜索到这个结点时,直接在 “备忘录” ⾥⾯找结果。其中,搜索树中的⼀个⼀个结点,也称为⼀个⼀个状态。 ⽐如经典的斐波那契数列问题: int f[N];// 备忘录intfib(int n){// 搜索之前先往备忘录⾥⾯瞅瞅if(f[n]!=-1)return f[n];if(n ==0|

By Ne0inhk