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

华为OD机试双机位C卷-统计员工影响力分数(C/C++/Py/Java/JS/GO)

华为OD机试双机位C卷-统计员工影响力分数(C/C++/Py/Java/JS/GO)

统计员工影响力分数 2025华为OD机试双机位C卷 - 华为OD上机考试双机位C卷 200分题型 华为OD机试双机位C卷真题目录点击查看: 华为OD机试双机位C卷真题题库目录|机考题库 + 算法考点详解 题目描述 假设你是大型科技公司的数据分析师,负责分析公司内部员工的社交网络。你需要编写一个函数来计算每个员工的影响力分数。影响力分数定义为该员工直接和间接影响的员工数量。 输入描述 n:员工总数。 employess:一个二维列表,表示员工的社交网络关系。例如employees[i]是一个包含员工i直接影响的员工ID的列表。 备注 employees列表中,* 表示没有直接影响到的员工;员工总数小于20;自身不算分数。 输出描述 influenceScores,一个整数数组,表示每个员工的影响力分数。 用例1 输入 4 1 2 3 * <

By Ne0inhk
Log4j 详解:Java经典日志框架

Log4j 详解:Java经典日志框架

在 Java 开发中,日志是排查问题、监控程序运行状态的核心手段。相比于System.out.println,专业的日志框架能提供更灵活的配置、更优的性能和更丰富的功能。本文将全面讲解 Log4j 的核心概念、配置方式及实战技巧,帮助你快速掌握并落地到项目中。 一、日志框架概述 1.1 什么是日志 日志是程序运行过程中产生的事件记录,典型场景包括: * 业务流程节点:如 “用户 ID=123 提交订单,订单号 = 456”; * 异常详情:捕获异常时记录完整堆栈信息(替代e.printStackTrace()); * 关键参数 / 结果:如接口入参、返回值; * 系统状态:如 JVM 内存使用、数据库连接池状态。 1.2 为什么要打印日志? 调试器断点仅适用于开发阶段,且可能因断点导致多线程问题 “隐藏”;而日志能持久化记录程序运行轨迹,

By Ne0inhk
Java 大视界 -- Java 大数据在智能教育在线实验室设备管理与实验资源优化中的应用(261)

Java 大视界 -- Java 大数据在智能教育在线实验室设备管理与实验资源优化中的应用(261)

💖亲爱的朋友们,热烈欢迎来到 青云交的博客!能与诸位在此相逢,我倍感荣幸。在这飞速更迭的时代,我们都渴望一方心灵净土,而 我的博客 正是这样温暖的所在。这里为你呈上趣味与实用兼具的知识,也期待你毫无保留地分享独特见解,愿我们于此携手成长,共赴新程!💖 全网(微信公众号/ZEEKLOG/抖音/华为/支付宝/微博) :青云交 一、本博客的精华专栏: 1. 大数据新视界专栏系列:聚焦大数据,展技术应用,推动进步拓展新视野。 2. Java 大视界专栏系列(NEW):聚焦 Java 编程,涵盖基础到高级,展示多领域应用,含性能优化等,助您拓宽视野提能力 。 3. Java 大厂面试专栏系列:提供大厂面试的相关技巧和经验,助力求职。 4. Python 魅力之旅:探索数据与智能的奥秘专栏系列:走进

By Ne0inhk
飞算JavaAI:重新定义研发效能,让代码生成如丝般顺滑

飞算JavaAI:重新定义研发效能,让代码生成如丝般顺滑

飞算JavaAI:重新定义研发效能,让代码生成如丝般顺滑 1. 摘要 在软件开发的浩瀚星空中,每一位程序员都渴望找到提升研发效能的金钥匙。本文将深入探讨飞算JavaAI这一革命性的智能编程助手,它不仅仅是一个代码生成工具,更是开发者效率提升的全新解决方案。通过深入剖析其本地化智能、精准的上下文理解和可控的代码生成机制,我们将揭示如何彻底改变传统软件研发模式。从根本上解决重复劳动、效率低下的痛点,飞算JavaAI正在重新定义Java开发的生产力边界,为开发者带来前所未有的编程体验。 2. 研发的痛与梦:智能编程助手的诞生背景 2.1. 传统研发的困境 每一位程序员都曾经历过这些令人沮丧的时刻: * 重复编写千篇一律的样板代码 * 在复杂项目中迷失代码架构 * 浪费大量时间在低价值的技术细节上 "程序员的价值不应该被重复性劳动消耗,而是应该专注于创新和解决实际问题。" —— 硅谷资深工程师 2.2. 智能编程的理想与现实 传统代码生成工具的局限性: 维度 传统工具 飞算JavaAI 上下文理解 有限 深度智能 本地化处理 依赖云端

By Ne0inhk