ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

天地图API 403排查:Referer白名单与Vue3部署实战

天地图API 403排查:Referer白名单与Vue3部署实战 不知道多少人被天地图的403折磨过。本地开发跑得好好的一部署到服务器地图图片全部加载不出来控制台红彤彤一片403 Forbidden看着就头大。我这次在Vue3项目里接天地图从本地联调没问题到服务器全挂再到最后定位到Referer白名单校验整个排查链路走下来花了不少时间。这篇就把天地图API返回403的成因、排查路径、解决方案以及部署到服务器后的各种环境差异问题一次讲完做前端、做全栈、或者负责运维部署的朋友都能直接用。先说个结论天地图返回403绝大部分情况不是你的代码写错了而是你的请求在天地图服务端那里身份核查没通过。理解这一点排查方向就对了。1. 403是谁发出来的天地图服务端的鉴权机制1.1 先把状态码含义搞对很多人一看到403就开始怀疑是不是key过期了、是不是跨域了、是不是要被封了其实这些猜测大多数方向都不对。HTTP状态码里401是说你没带凭证或者凭证根本不存在而403是凭证带了但我核对之后决定不让你过。这两个含义差得非常远如果混着排查你会一直去重新申请key、重新配白名单但问题并不在那一层。天地图返回403至少说明一个关键事实你的请求确实到达了天地图的服务器。它不是被自己的Nginx拦截了也不是被本地防火墙吞了而是天地图接收请求后主动拒绝的。这个信息非常值钱它直接把排查范围缩小到了请求参数和来源合法性这两个方向。1.2 密钥与应用类型的区别天地图的API鉴权靠的是两层校验一个是你的密钥tk另一个是调用来源的合法性。在天地图控制台申请应用后你会拿到一个几十位的字符串这就是tk。所有请求都要带上它。但光有tk还不够天地图的密钥分两种应用类型浏览器端应用面向网页场景key绑定的是域名白名单。天地图服务端会读请求头里的Referer字段判断这个请求是不是从白名单内的域名发出的。服务端应用面向后端服务器调用key绑定的是服务器出口IP白名单。这种模式不校验Referer但会校验请求来源IP是否在白名单里。结合Vue3项目最常见的错误形态来看逻辑就非常清晰你本地跑npm run dev页面地址是http://localhost:5173申请key时顺手把localhost:5173加进了白名单于是联调一切正常。部署后页面地址变成https://map.company.comReferer自然跟着变如果控制台里没把新域名加进去结果就是403 Forbidden。1.3 鉴权链路token 来源校验把天地图的鉴权流程理解成进小区就很好记了tk相当于你的门禁卡门禁卡本身要有记录、要在有效期内。Referer相当于门禁系统记录的访客来源楼栋门禁系统得知道你这栋楼是允许进入的。门禁卡刷了但系统发现你来路不明照样锁死。所以天地图服务端收到请求后实际上是连续做了两道检查先看tk参数是否存在、是否有效然后再看请求源头是否在白名单内。任何一道不过返回的都是403。这个机制也解释了为什么本地能跑、线上403和线上正常、本地403这两种情况同时存在——它们分别对应不同的白名单配置域名对不上号就进不去。2. 头号原因referer白名单与域名绑定2.1 为什么本地一跑就通、部署就403这是我在开发中被问得最多的问题。如果你用的是天地图的浏览器端key那九成就是Referer白名单的问题。天地图控制台在创建浏览器端应用时会让你填写域名白名单。服务端校验时会拿请求头里的Referer字段去比对。本地开发时你填的大概率是http://localhost:5173或http://localhost这个地址和本地实际发出的保持一致所以通过。一旦部署页面发起请求的Referer变成https://你的域名白名单里没有这个域名403就来了。还有一个容易踩的坑团队在测试环境配置过域名上线时用同一套key新域名没有新增进去于是线上403。我个人的习惯是每个环境用独立的key测试域名和生产域名分开配置这样改来改去互不影响也方便控制台看调用量统计时区分来源。2.2 在天地图控制台正确配置白名单登录天地图控制台后大致路径是进入应用开发列表找到对应应用点编辑在允许访问的域名配置区域添加新的域名。填写时注意下面几点域名尽量写完整。推荐直接填写完整的参考来源比如https://map.example.com带上协议、域名、端口号。如果本地是http://localhost:5173就完整写http://localhost:5173不要偷懒只写localhost否则校验可能不通过。端口号不要漏。很多本地服务用的不是80端口开发服务器一般是5173、8080之类这些端口号都要写进去。通配符酌情使用。天地图控制台支持*.example.com这类泛域名写法但如果你的域名是固定的还是建议写具体域名减少误伤面。改完多久生效。正常是立即生效但浏览器端在极端情况下可能有缓存残留。如果确认配了还是403先强制刷新页面再验证或换个隐身窗口测试。2.3 验证Referer到底发没发出去配置了白名单但还是403就要确认浏览器到底有没有把Referer带过去。最直接的办法是打开开发者工具切到Network面板刷新页面找到天地图瓦片或API的请求查看请求头里的Referer值。正常情况下你应该能看到类似https://map.example.com/这样的字段。如果你看到的Referer是空的那问题就不在白名单而在页面的Referrer Policy设置。有些工程会全局加meta namereferrer contentno-referrer这会导致所有请求都不带来源信息天地图无法判断你的页面来自哪个域名直接403。还有HTTPS页面请求HTTP资源时浏览器默认不发送Referer但天地图本身是HTTPS一般不会触发。如果动态创建script加载天地图可以给script设置referrerPolicyorigin保证来源信息不会丢失const script document.createElement(script) script.src https://api.tianditu.gov.cn/api?v4.0tk${tk} script.referrerPolicy origin script.onload () { initMap() } document.head.appendChild(script)3. Vue3项目接入天地图的完整流程3.1 动态加载JS APIVue3项目里接入天地图我一般不在index.html里直接写死script标签而是在用到地图的组件里动态加载。好处是路由懒加载的页面不会一进应用就下载地图脚本首屏压力小代码层面就是个Promise封装// src/utils/tianditu.js let loadingPromise null export function loadTianditu(tk) { if (window.T) { return Promise.resolve(window.T) } if (!loadingPromise) { loadingPromise new Promise((resolve, reject) { const script document.createElement(script) script.src https://api.tianditu.gov.cn/api?v4.0tk${tk} script.referrerPolicy origin script.onload () resolve(window.T) script.onerror () { loadingPromise null reject(new Error(天地图API加载失败)) } document.head.appendChild(script) }) } return loadingPromise }这里把loadingPromise缓存起来是为了防止多个组件同时调用时重复创建多个script标签。虽然是小事但地图组件在Vue3里经常被复用踩过一次重复加载的坑之后我就学乖了。3.2 创建地图实例加载完成后用全局对象T创建地图template div idmapContainer classmap-box/div /template script setup import { onMounted, onBeforeUnmount } from vue import { loadTianditu } from /utils/tianditu const tk import.meta.env.VITE_TIANDITU_TK let map null onMounted(async () { const T await loadTianditu(tk) map new T.Map(mapContainer, { projection: EPSG:4326, center: new T.LngLat(116.407, 39.904), zoom: 12 }) const layer new T.TileLayer({ urlTemplate: https://t0.tianditu.gov.cn/vec_w/wmts?SERVICEWMTSREQUESTGetTileVERSION1.0.0LAYERvecSTYLEdefaultTILEMATRIXSETwFORMATtilesTILEMATRIX{z}TILEROW{y}TILECOL{x} }) map.addLayer(layer) }) onBeforeUnmount(() { if (map) { map.clearAll() map null } }) /script注意这段代码的瓦片urlTemplate里没有拼tk原因是我在加载脚本时已经带上了key天地图JS API会把密钥自动透传到瓦片请求上。如果你自己封装瓦片地址也可以手动加tk你的key但容易遇到key拼接错误、参数被覆盖之类的问题优先复用官方机制就好。3.3 开发环境的跨域代理天地图的瓦片加载走的是图片或Canvas请求不存在跨域限制。但如果你调用了天地图的纯数据接口比如地理编码、逆地理编码直接从浏览器fetch是会有跨域问题的。本地开发时用Vite代理解决// vite.config.js export default defineConfig({ server: { proxy: { /tianditu: { target: https://api.tianditu.gov.cn, changeOrigin: true, rewrite: (path) path.replace(/^\/tianditu/, ) } } } })请求写成fetch(/tianditu/geocoder?dsxxx)开发环境会自动转发到https://api.tianditu.gov.cn跨域问题就绕过去了。3.4 多环境key管理我把天地图的key放在环境变量里而不是硬编码在组件代码中# .env.development VITE_TIANDITU_TK开发环境的key # .env.production VITE_TIANDITU_TK生产环境的key这样同一套仓库代码不同环境构建时取到的tk不同配合不同环境配置的白名单能省掉大量环境切换导致403的排查时间。这个实践推广到任何第三方服务的密钥管理都适用不止天地图一个场景。4. 部署到服务器后的Nginx与代理排查4.1 先在浏览器里确认请求有没有发出去部署之后遇到403不要急着改Nginx先在浏览器Network面板里确认两件事请求URL是否完整tk参数是否正常出现在请求里Referer字段是否是当前线上域名请求是否被重定向过重定向后Referer有可能变化。这一步的核心是把问题分类如果请求根本没发出去问题在你的域名解析、防火墙、代理层如果请求已经发出去但返回403问题在天地图侧的鉴权参数。很多人在这一层没做区分就盲目去改服务配置结果越改越乱。4.2 Nginx反向代理时要透传Referer如果你的页面通过Nginx反向代理出去这里有一个容易忽略的细节默认情况下Nginx会透传原始HTTP请求头但如果你手动配置了proxy_set_header块就很可能把Referer覆盖或抹掉。排查时确认配置里有这一行location / { proxy_set_header Host $http_host; proxy_set_header Referer $http_referer; proxy_set_header X-Real-IP $remote_addr; proxy_pass http://你的后端地址; }$http_referer就是原始请求头里的Referer值。有这一行天地图才能拿到页面的真实来源。如果没有或者有人图省事写死了一个:proxy_set_header Referer http://localhost;线上不403才怪。4.3 服务器访问公网受限怎么看还有一种403跟天地图完全无关出现在服务器访问不了公网的场景。部分内网部署环境的服务器出口受限访问api.tianditu.gov.cn会被安全组或防火墙拦截。这种情况下你本机浏览器访问是正常的但服务器上的后端请求、或者某些需要服务端发起校验的环节就会一直表现成403。判断方法很简单直接在服务器上执行curl -sS -o /dev/null -w %{http_code} https://api.tianditu.gov.cn/api?v4.0tk你的key返回200说明网络通返回403或超时说明不是被拦截就是白名单没配。这一条能快速区分网络层和鉴权层的问题省得在两边反复横跳。4.4 让服务器日志开口说话排查的时候别只盯浏览器Network还要看服务器日志。Nginx默认日志路径常见的是/var/log/nginx/access.log可以实时观察tail -f /var/log/nginx/access.log | grep tianditu这样你能看到每个天地图相关请求的状态码、耗时、来源Referer。有时候浏览器显示403但后端日志里显示500或502这说明问题根本不在天地图而在你自己的服务链路。前端的一个报错信息不一定代表后端也是同一个错误类型。5. 服务端中转最稳妥的兜底方案如果你的项目环境没法控制Referer比如微信小程序、uni-app、桌面客户端、以及一些奇怪的WebView容器浏览器端key的鉴权方式会变得不可控。这时候最稳的方案就是把天地图的调用挪到服务端由你的后端统一向天地图请求前端只与自己的后端交互。5.1 申请服务端key并绑定IP白名单在天地图控制台创建应用时应用类型选择服务端填写服务器的公网出口IP。这里有一个容易错的点必须填准确的出口IP不是内网IP也不是负载均衡的虚拟IP。可以让服务器自己查一下curl ifconfig.me如果服务器有多个出口IP或者部署在多地域把所有出口IP都加进去。服务端key的鉴权就看请求来源IP在不在白名单内漏一个就403一个。5.2 Node.js中间层转发示例这里给一个Express的简化实现实际项目中可以封装到你的网关服务里const express require(express) const axios require(axios) const router express.Router() const TIANDITU_BASE https://api.tianditu.gov.cn const SERVER_TK process.env.TIANDITU_SERVER_TK router.get(/proxy/tianditu/:path(*), async (req, res) { try { const targetUrl ${TIANDITU_BASE}/${req.params.path}?${new URLSearchParams({ ...req.query, tk: SERVER_TK }).toString()} const response await axios.get(targetUrl, { responseType: arraybuffer, headers: { User-Agent: YourAppName/1.0 } }) res.set(Content-Type, response.headers[content-type]) res.set(Cache-Control, public, max-age86400) res.send(Buffer.from(response.data)) } catch (err) { if (err.response) { res.status(err.response.status).send(err.response.statusText) } else { res.status(502).send(Bad Gateway) } } }) module.exports router这里有个细节服务端请求不需要Referer但最好带上一个定制User-Agent方便在天地图控制台排查错误日志时定位到自己的应用。我还加了Cache-Control让瓦片能被浏览器HTTP缓存命中地图拖拽体验会好很多。生产环境还可以加一层Redis缓存把高频瓦片缓存到本地减少对天地图的直接调用量。5.3 前端调用方式调整Vue3前端就不需要再暴露天地图的key了请求指向自己的服务const layer new T.TileLayer({ urlTemplate: /proxy/tianditu/vec_w/wmts?SERVICEWMTSREQUESTGetTileVERSION1.0.0LAYERvecSTYLEdefaultTILEMATRIXSETwFORMATtilesTILEMATRIX{z}TILEROW{y}TILECOL{x} })如果你的后端不是Node.js用Java、Go、Python写都没问题核心思路一致key放服务端请求从服务端发出IP白名单负责鉴权。6. 高频翻车点清单与快速定位技巧最后把实际项目中踩过、见过的翻车点整理成速查表方便遇到403时对照排查。很多问题表现形式都是403但成因完全不同。现象原因处理方式本地正常线上403Referer白名单没加线上域名控制台添加线上完整域名本地也403tk不正确或应用未生效核对tk确认应用状态Referer显示为空页面设置了no-referrer策略调整referrer policyfile://直接打开403无Referer或来源为null用本地服务启动页面服务端调用403出口IP不在白名单申请服务端key并添加IP环境切换后403同一key访问多个域名每个环境独立key隐私模式/插件拦截扩展程序改写了Referer换普通窗口验证偶发403刷新后正常浏览器缓存了旧请求加版本号参数强制刷新6.1 用curl快速复现403遇到403先不要反复刷新页面用curl发一个请求是剥离浏览器环境最快的方式curl -sS -o /dev/null -w %{http_code} \ -H Referer: https://你的域名/ \ https://api.tianditu.gov.cn/api?v4.0tk你的key返回200说明天地图侧校验没问题问题在浏览器行为或页面网络环境。返回403说明key、Referer白名单、IP三者中至少有一项不对换成白名单里的域名再试逐步二分定位。6.2 容易被缓存干扰的坑配置了白名单并确认无误后还是偶发403可以怀疑浏览器缓存了旧的天地图脚本或瓦片。强制刷新一次或者给请求加版本号参数例如JS API地址加_t时间戳能减少这类缓存干扰。实际排查中这个坑不容易想到但确实会让人误判问题仍然存在。6.3 天地图服务自身的可用性还有一种情况你的配置全对但天地图侧因为自身维护或调用量配额用尽返回异常。可以去天地图开放平台官网看看服务公告或者到控制台的调用量统计里看当天请求曲线。如果控制台显示的调用量已经到顶那就是配额问题需要申请调整配额或临时换一个key过渡。经过这么一轮排查绝大多数天地图403都能定位到根因要么是Referer白名单没配全要么是服务端IP白名单不对要么是请求在代理层把来源信息弄丢了。我自己现在的习惯是不管多熟悉的流程每次新项目接入天地图都先从第一步就区分清楚应用类型——前端页面就用浏览器端key搞域名白名单服务端调用就用服务端key配IP白名单。这个决定会贯穿整个项目后续的每次部署。最后再分享一个调试小技巧排查天地图403时把要验证的域名逐个用curl加上Referer跑一遍返回200的直接放行返回403的立刻回控制台对照白名单逐字比对。这个方法具体、快速比在浏览器里一次次刷新乱猜高效得多建议收藏备用。
返回列表