天地图 WMTS 403 Forbidden:密钥、白名单与代理配置全解析
天地图这套服务做过 GIS 开发的应该都用过或者至少听说过。它算是国内做地图底图接入时绕不开的一个选项尤其是项目里有涉密要求、不希望依赖境外地图服务的场景。但它的密钥体系和调用限制也让不少人第一次接的时候就卡在403 Forbidden上。我最近帮一个做园区数字孪生的朋友排查问题前端页面加载天地图底图全部空白控制台一水的 403从申请 key 到代理转发倒腾了大半天最后发现是浏览器端密钥被用在了服务器上。类似这种看着像服务器拒绝访问的报错背后原因其实分好几层从密钥类型、白名单、配额到反向代理的 Referer 头每一层都可能让请求被拦下来。这篇文章我打算把它讲透403 到底是谁返回的、怎么一步步定位、浏览器端和服务端密钥的区别在哪、ArcGIS Pro 和常用 JS 库怎么正确配置、以及我踩过的几个坑。无论你是刚接触天地图的新手还是已经接了但偶尔翻车的开发者都能从里面找到能直接抄的排查流程和配置片段。1. 先把 403 的来龙去脉搞清楚谁在拒绝你很多人一看 403 就以为是服务器挂了其实这个状态码的含义是服务器理解了你的请求但拒绝执行。放在天地图场景里能返回 403 的环节至少有三处搞清楚是哪一处返回的排查效率能提升一大截。1.1 天地图的服务架构与请求链路天地图的瓦片服务不是一个单一域名而是一组分发节点通常是t0.tianditu.gov.cn到t7.tianditu.gov.cn数字越大代表不同的 CDN 节点。你的请求链路大概是这样浏览器或你的后端服务发起请求 → 携带tk参数和Referer头 → 到达天地图的接入网关 → 网关校验密钥有效性和来源 → 校验通过后回源到瓦片存储 → 返回对应的 PNG/JPG 瓦片或者 XML 说明。这里有个容易被忽略的点同一个 key访问t0和访问t5的效果不一定完全一样。因为不同节点的缓存状态、健康状态可能不同偶尔会出现某个节点抽风返回 403换一个节点就正常了。所以排查的时候换节点是成本最低的一个验证动作。另一个关键点是天地图的 WMTS 服务和它的服务类型是绑定的。矢量底图、影像底图、地形晕渲、注记层各自是独立的服务路径。你申请的 key 如果只勾选了矢量底图权限去请求影像服务同样会被拒绝。这一点在控制台的应用详情里能看到授权范围但很多人申请完就不看了。1.2 403 Forbidden 在天地图场景下的几层含义我把常见的 403 分成四类理解这四类基本就理解了排查方向。第一类是密钥层拒绝tk没传、传错、过期、未激活、被回收、应用类型不对。这是最常见的一类也是新手最容易踩的。第二类是来源层拒绝浏览器端密钥靠Referer校验服务端密钥靠来源 IP 校验。本地开发时Referer是localhost如果白名单里没填这个直接 403。第三类是配额层拒绝key 有日调用量上限和并发上限超了之后网关会拒绝后续请求。有些场景下超限返回的不是 403 而是别的错误码但 403 也见过所以要结合控制台的调用统计来判断。第四类是参数层拒绝URL 里参数写错、层级越界、矩阵集不匹配。这类严格说可能返回 400 而不是 403但天地图的错误返回有时候不够规范混着来不能只靠状态码判断。提示排查任何 403第一步永远是把完整请求 URL 复制到浏览器地址栏直接回车看返回的到底是什么内容。返回的正文比状态码本身信息量大得多。我见过有人一上来就怀疑网络、怀疑防火墙折腾半天结果发现是tk参数拼在 URL 里被某个中间件吃掉了。所以顺序不能乱先确认请求本身对不对再确认来源对不对最后才看配额和网络。2. tk密钥本身的五个致命细节密钥问题占了 403 的一大半。天地图的密钥体系看起来简单一个tk字符串而已但底下藏着不少细节我逐个说。2.1 应用类型选错浏览器端 vs 服务端这是我认为最容易翻车的地方也是我自己第一次接天地图时踩的坑。在天地图控制台创建应用的时候会让你选应用类型一般分为浏览器端和服务端两类。名字看着朴素含义差别很大。浏览器端密钥校验的是Referer也就是请求来自哪个网页域名。你在控制台填的授权域名必须和实际请求的Referer匹配。适合前端 JS 直接加载瓦片的场景。服务端密钥校验的是来源 IP适合放在后端服务里调用或者做代理转发。它不看Referer看的是你服务器出口 IP 是否在白名单里。把浏览器端密钥拿去后端调用或者把服务端密钥拿去前端页面用基本都会 403。原因是一个没有 IP 白名单校验通过一个没有 Referer 匹配。我朋友那次就是这个情况前端用的是后端同事申请的服务端 key本地测试全过一部署到浏览器就全 403。注意如果你项目里既有前端直接调又有后端代理调最省事的做法是在控制台申请两个应用一个浏览器端一个服务端各用各的 key别指望一个 key 通吃。还有一点浏览器端密钥的授权域名配置里一般不允许随便填通配符或者*具体规则以控制台提示为准。本地开发如果用的是localhost:8080这种记得把localhost加进白名单否则本地就调不通。2.2 白名单不匹配域名与 IP白名单不匹配是个很难受的问题因为它的表现和密钥无效很像都是 403看不出区别。域名白名单的场景下你要关注的是Referer头里到底带的是什么。用file://协议打开本地 HTML 文件时Referer是空的或者null这种情况大概率被拒。用localhost开发时Referer是http://localhost:端口白名单里只填了localhost不一定匹配端口号有时候也算在内建议按控制台给的格式填。IP 白名单的场景下你要关注的是你服务的出口 IP不是你本机的 IP。如果你部署在云服务器上出口 IP 可能是 NAT 之后的地址和你在控制台看到的实例内网 IP 完全不是一回事。这种情况下最直接的办法是在服务器上执行curl ifconfig.me或者类似命令看看自己真实的出口 IP 是多少再填进白名单。还有一种坑部分云服务商的机器的出口 IP 是动态的重启后可能变。如果白名单只填了一个固定 IP重启之后 key 就失效了。这种场景建议把可能的 IP 段都加上或者走代理转发方案统一出口。2.3 key 未激活、被回收与配额耗尽天地图的 key 申请之后有些情况下需要等待生效也有些情况下需要人工审核。刚申请完就着急用发现 403先别怀疑代码去看看控制台里这个应用的状态是不是已生效。如果显示审核中或者未启用那就是这个原因。被回收的情况一般是长期不用或者曾经有过异常调用被系统判定为滥用。这种情况控制台通常会有提示看应用的调用统计和状态就能判断。配额耗尽比较隐蔽。天地图的 key 一般有日调用量上限和 QPS 上限。地图瓦片这东西请求量大一个页面滑动几下就是几十上百个请求稍微有点量的系统很容易把日调用量跑满。跑满之后返回 403 或者类似错误第二天零点或者一定时间后自动恢复。排查方法登录控制台看应用详情的调用统计图表如果曲线在某天明显拉满基本可以确认。解决办法是申请调用量更高的配额或者给瓦片加本地缓存减少对天地图的重复请求。2.4 服务路径与矩阵集参数写错天地图的 URL 参数不算复杂但细节多写错了就会 403 或返回空瓦片。一个标准的矢量底图瓦片请求长这样https://t0.tianditu.gov.cn/vec_w/wmts?SERVICEWMTSREQUESTGetTileVERSION1.0.0LAYERvecSTYLEdefaultTILEMATRIXSETwFORMATtilesTILEMATRIX10TILEROW384TILECOL843tk你的密钥这里面几个关键点vec_w是路径vec代表矢量底图_w代表球面墨卡托投影矩阵集。如果你要用经纬度投影路径是vec_c对应TILEMATRIXSETc。LAYERvec要和路径对应。路径写vec_w参数里写LAYERcva就会被拒。TILEMATRIXSETw也要和路径对应路径是_w参数就得是w。FORMATtiles基本是固定值。TILEMATRIX是层级TILEROW是行号TILECOL是列号。常见错误是路径用了_c但参数里TILEMATRIXSET还留着w这种不一致往往导致服务端直接拒绝。还有一种是把LAYER写成了中文服务名比如写矢量底图那必然不行。2.5 层级越界与参数缺失天地图各服务的可用层级是有限的。一般来说球面墨卡托矩阵集w支持到 18 级经纬度矩阵集c支持到 18 级左右具体以官方文档为准。请求超过上限的层级比如TILEMATRIX22可能返回空白瓦片也可能直接 403。参数缺失也容易被忽略。SERVICE、REQUEST、VERSION、LAYER、STYLE、TILEMATRIXSET、FORMAT、TILEMATRIX、TILEROW、TILECOL、tk这些基本都是必需的。少一个服务端可能不明确报错直接甩个 403 给你。写 URL 的时候建议用模板变量生成别手拼容易漏。3. 排查实操从浏览器到命令行的完整定位流程理论说完了接下来是我实际用的排查流程。这套流程从最外层往里收基本能在十分钟内定位到问题。3.1 浏览器直接访问 URL 的验证法第一步把你在代码里拼出来的完整请求 URL原封不动复制到浏览器地址栏回车。如果浏览器显示了一张瓦片图片说明 URL 和 key 本身没问题问题出在你代码发起请求的方式上比如跨域、Referer 头被改、请求被拦截。如果浏览器返回一个 403 页面或者一段 XML那就要看这段内容。天地图在密钥错误时有时会返回类似无权限访问或者key 无效的说明文字虽然不一定特别详细但能帮你区分是密钥层还是来源层的问题。如果返回的是完全无关的内容比如某个云厂商的错误页那可能是你的域名解析或者代理配置有问题请求根本没到天地图。这一步的价值就在于它排除了你自己代码引入的变量。代码里拼的 URL 和浏览器里访问的 URL 只要有一个字符不同结论就可能完全不一样所以要复制而不是手敲。3.2 DevTools Network 面板的读法浏览器验证通过但页面上还是加载不出来这时候打开 F12 的 Network 面板筛选出失败的请求看几个关键字段。看Status Code是 403 还是 404 还是别的。403 是权限问题404 是路径问题方向完全不同。看Request URL和自己预期的 URL 逐字符比对尤其是tk参数有没有被截断、有没有重复的、有没有被编码成了别的字符。看Request Headers里的Referer这就是天地图用来校验的值看它和你白名单里填的是否一致。注意Referer只包含协议、域名、端口和路径不带查询参数。看Response返回的正文内容经常藏着真相别只看状态码。我遇到过一次情况前端框架的路由把页面地址改成了带 hash 的形式Referer是带 hash 的完整地址白名单里只填了域名结果不匹配。虽然理论上 hash 不该进 Referer但某些构建工具或者中间层会加进去。遇到这种玄学问题老老实实打印Referer看就完了。3.3 curl 复现与 Referer 验证浏览器上不方便改Referer用 curl 就方便多了。下面这条命令可以在命令行里模拟带 Referer 的请求curl -v -H Referer: https://你的域名/ https://t0.tianditu.gov.cn/vec_w/wmts?SERVICEWMTSREQUESTGetTileVERSION1.0.0LAYERvecSTYLEdefaultTILEMATRIXSETwFORMATtilesTILEMATRIX10TILEROW384TILECOL843tk你的密钥 -o test.png-v会打印完整的请求和响应头你可以看到Referer是否被正确带上以及响应状态。-o test.png把返回内容存成文件如果是一张正常的瓦片文件会有明显的图片大小如果只有几百字节多半是错误页。通过改Referer的值可以快速验证是不是 Referer 导致的 403。比如把Referer换成白名单里填的域名如果请求成功了那就确认是白名单问题接下来只需要把正确的域名加进白名单或者调整代码里请求的来源。同样的方法可以验证服务端 key 的 IP 问题。在服务器上执行这条 curl如果服务器出口 IP 不在白名单就会 403换到白名单里的服务器上执行就成功。这样能精确定位到 IP 层面。3.4 服务端代理方案把 key 藏起来前端直接调天地图有个绕不开的问题key 暴露在浏览器里任何人打开 F12 都能拿到然后拿去自己的项目里刷你的配额。虽然白名单能限制来源域名但被刷的风险依然存在。所以我一般建议做一个轻量的服务端代理。思路很简单前端请求你自己的服务你的服务再带上服务端 key 去请求天地图把瓦片原样返回。这样 key 只在服务器上前端完全看不到。同时服务端可以在代理层做缓存把常用瓦片缓存在本地磁盘或者 Redis 里重复请求不再打到天地图配额压力大幅下降。用 Nginx 做这个代理是最省事的不需要写代码location /tianditu/ { proxy_pass https://t0.tianditu.gov.cn/; proxy_set_header Host t0.tianditu.gov.cn; proxy_set_header Referer ; proxy_ssl_server_name on; proxy_cache my_cache; proxy_cache_valid 200 7d; proxy_cache_key $request_uri; add_header X-Cache-Status $upstream_cache_status; }这段配置有几个关键点。proxy_set_header Referer 是把前端带来的 Referer 清掉避免污染对天地图的请求服务端 key 靠的是 IP 白名单不依赖 Referer所以清掉反而更干净。proxy_cache相关几行开启缓存瓦片这种内容基本不变的资源缓存一周甚至更久都没问题。proxy_ssl_server_name on在 TLS 场景下是必需的否则 SNI 不对握手可能失败。前端那边就把原来的天地图 URL 换成你自己的域名const urlTemplate https://你的域名/tianditu/vec_w/wmts?SERVICEWMTSREQUESTGetTileVERSION1.0.0LAYERvecSTYLEdefaultTILEMATRIXSETwFORMATtilesTILEMATRIX{z}TILEROW{y}TILECOL{x}tk你的服务端密钥;注意这里tk可以保留在 URL 里因为它是服务端密钥靠 IP 校验暴露了也刷不动白名单外调不了。也可以干脆在 Nginx 层统一追加tk前端完全不传这样更干净。实操心得代理层的缓存目录建议单独挂一块盘别和系统盘混。瓦片文件小而多inode 消耗很快不然磁盘没满但 inode 先耗尽缓存写入失败还不容易发现。3.5 常见的代理层踩坑代理方案虽然简单也有坑。第一个坑是proxy_pass后面带不带路径的差别。proxy_pass https://t0.tianditu.gov.cn/;带结尾斜杠会把location匹配到的前缀替换掉不带斜杠则是把整个 URI 拼上去。天地图瓦片请求的路径必须精确配错了就会 404 或者 403。第二个坑是 HTTPS 证书。天地图服务是 HTTPS 的Nginx 反向代理到 HTTPS 上游时需要配置proxy_ssl_server_name on否则可能报证书错误。有些老版本 Nginx 还需要手动指定proxy_ssl_name。第三个坑是缓存 key。默认的$request_uri包含了tk参数如果 tk 变了缓存 key 也变了会重复缓存。建议用proxy_cache_key手动拼一个不含 tk 的 key比如$scheme$host$uri$is_args$args再把 tk 剔掉或者干脆在代理层统一注入 tk 之后再拼缓存 key。第四个坑是超时。瓦片请求本身很快但如果上游抖动默认的 60 秒超时会让前端一直转圈。建议把proxy_connect_timeout、proxy_read_timeout都设短一点比如 5 到 10 秒失败快速返回前端可以重试。4. ArcGIS Pro / JS API 加载天地图的正确姿势前面讲的是通用排查和代理接下来讲具体工具链的配置。ArcGIS 系列和常用前端地图库加载天地图的方式差异不小配置里任何一处不对都会 403。4.1 ArcGIS Pro 连接 WMTS 的步骤ArcGIS Pro 加载天地图推荐走 WMTS 服务的方式而不是早期那种自定义切片的方式。大致步骤如下第一步打开插入选项卡找到连接里的服务器选择新建 WMTS 服务器连接。第二步在 URL 里填写 WMTS 服务的地址。这里要注意GetCapabilities 请求也需要带 tk所以 URL 要写成https://t0.tianditu.gov.cn/vec_w/wmts?tk你的浏览器端或服务端密钥ArcGIS Pro 会先请求 GetCapabilities 拿到服务元数据再根据元数据加载瓦片。如果这一步 403说明 key 有问题或者 IP/域名不在白名单。第三步连接成功后在目录树里会看到这个 WMTS 服务把需要的图层拖进地图即可。这里有几个注意点。第一如果你在桌面端用填的 key 建议用服务端类型因为 ArcGIS Pro 发出的请求 Referer 不固定浏览器端 key 容易匹配不上。第二同一台机器如果出口 IP 变了key 又会失效所以生产环境尽量固定在白名单内的机器上操作。第三ArcGIS Pro 对 WMTS 的版本和参数支持可能有细节要求遇到加载不出来先在浏览器里访问一次 GetCapabilities 的 URL确认服务本身是好的再排查 Pro 的配置。另外如果把天地图作为底图图层加入注意正确设置坐标系。天地图的_w系列是球面墨卡托对应 EPSG:3857_c系列是经纬度对应 EPSG:4490 或 4326选错了坐标系瓦片位置会完全错位看着像加载失败其实只是叠在了别处。4.2 ArcGIS JS API 的 WebTileLayer用 ArcGIS Maps SDK for JavaScript 加载天地图标准做法是用WebTileLayer把 URL 模板拼好require([esri/layers/WebTileLayer, esri/Map, esri/views/MapView], function (WebTileLayer, Map, MapView) { const vecLayer new WebTileLayer({ urlTemplate: https://t0.tianditu.gov.cn/vec_w/wmts?SERVICEWMTSREQUESTGetTileVERSION1.0.0LAYERvecSTYLEdefaultTILEMATRIXSETwFORMATtilesTILEMATRIX{level}TILEROW{row}TILECOL{col}tk你的密钥 }); const cvaLayer new WebTileLayer({ urlTemplate: https://t0.tianditu.gov.cn/cva_w/wmts?SERVICEWMTSREQUESTGetTileVERSION1.0.0LAYERcvaSTYLEdefaultTILEMATRIXSETwFORMATtilesTILEMATRIX{level}TILEROW{row}TILECOL{col}tk你的密钥 }); const map new Map({ basemap: { baseLayers: [vecLayer], title: 天地图矢量 } }); const view new MapView({ container: viewDiv, map: map, center: [116.4, 39.9], zoom: 10 }); map.add(cvaLayer); });几个关键点。{level}、{row}、{col}是 WebTileLayer 支持的占位符对应瓦片的层级、行号、列号别写成了{z}、{y}、{x}那样不会被替换请求就会报错。底图用矢量底图vec_w注记用cva_w两个图层叠加才有地名标注。如果只加载vec_w没有注记看着像底图有了但没字不是 403别搞混。如果你的 key 是浏览器端类型前端页面部署的域名必须和配置的授权域名一致本地开发要注意 localhost 也要加白名单。如果不一致控制台会看到一片 403。4.3 OpenLayers / Leaflet / Cesium 的写法差异不同库的占位符和加载方式差异不小混用容易出错。OpenLayers 用WMTS源或者XYZ源都行。用XYZ源的话占位符是{z}、{y}、{x}new ol.layer.Tile({ source: new ol.source.XYZ({ url: https://t0.tianditu.gov.cn/vec_w/wmts?SERVICEWMTSREQUESTGetTileVERSION1.0.0LAYERvecSTYLEdefaultTILEMATRIXSETwFORMATtilesTILEMATRIX{z}TILEROW{y}TILECOL{x}tk你的密钥 }) });Leaflet 用L.tileLayer占位符同样是{z}、{y}、{x}L.tileLayer(https://t0.tianditu.gov.cn/vec_w/wmts?SERVICEWMTSREQUESTGetTileVERSION1.0.0LAYERvecSTYLEdefaultTILEMATRIXSETwFORMATtilesTILEMATRIX{z}TILEROW{y}TILECOL{x}tk你的密钥, { maxZoom: 18, subdomains: [t0, t1, t2, t3] }).addTo(map);注意 Leaflet 的subdomains选项可以轮询使用多个天地图节点对分散压力、避免单节点限流有好处。Cesium 加载天地图走WebMapTileServiceImageryProviderconst provider new Cesium.WebMapTileServiceImageryProvider({ url: https://t0.tianditu.gov.cn/vec_w/wmts?tk你的密钥, layer: vec, style: default, format: tiles, tileMatrixSetID: w, maximumLevel: 18 });Cesium 这里有个坑url只需要带tk其他参数由 Cesium 内部拼如果手拼了整个 URL 又会和内部参数冲突容易报错或 403。所以最好按文档给的写法来别图省事把完整 URL 丢进去。注意不管用哪个库先确认自己用的坐标系和天地图服务匹配。_w是 3857_c是 4490/4326三个维度——投影、矩阵集、层级范围——任何一个对不上都可能表现成加载失败或 403。5. 常见403场景速查表与避坑心得最后一节我把常见问题整理成速查表再加上我踩过的几个坑遇到问题的时候可以直接对照。5.1 问题速查表现象可能原因排查动作解决方式浏览器直接访问 URL 也 403key 无效、类型不对、配额耗尽看返回正文查控制台应用状态和统计换正确类型的 key、补充配额浏览器访问正常页面加载 403Referer 不在白名单看 Network 里实际 Referer把实际域名加进白名单本地开发 403部署后正常localhost 没进白名单看本地请求 Referer把 localhost 及端口加白名单服务器 curl 403本机正常出口 IP 不在白名单服务器上查出口 IP把出口 IP 加入白名单换了台服务器就 403出口 IP 变化对比新旧 IP用代理统一出口或补 IP跑了半天突然全 403日调用量或 QPS 超限看控制台调用统计曲线加缓存、换更高配额某些层级 403 / 空白请求层级越界检查 TILEMATRIX 值限制在服务支持层级内加载没报错但地图错位坐标系不匹配核对_w/_c与地图坐标统一坐标系底图有但无地名注记只加载了底图未加载注记查看图层列表叠加cva_w注记层这张表我建议先照着现象找行再按排查动作走一遍覆盖率挺高的。5.2 我踩过的几个坑第一个坑浏览器端和服务端 key 混用。这个我前面说过但真的是最高频的问题。判断方法很简单看控制台里这个应用的应用类型如果是浏览器端必须走 Referer 白名单如果是服务端必须走 IP 白名单。两者不通用。第二个坑Referer 被中间层改掉。有一次项目里加了个网关网关在转发时把Referer清空了前端请求是正常的但到了天地图那边 Referer 是空的直接 403。排查的时候只看前端 DevTools 是看不出来的得看网关日志或者后端收到的请求头。这个坑提醒我排查请求问题要沿着链路一段段看不能只看起点。第三个坑HTTPS 页面请求 HTTP 接口。天地图早期有人用 HTTP 的瓦片地址页面升级到 HTTPS 之后浏览器会拦截混合内容请求表现可能是请求被阻也可能是 403。解决办法就是把瓦片地址也换成 HTTPS。第四个坑本地文件协议打开网页。有的同学写了个 HTML 直接双击打开用的是file://协议这种场景下 Referer 可能为空或不规范白名单自然也匹配不上。正式测试一定要起一个本地服务用http://localhost访问。第五个坑key 抄错或者被截断。天地图的 tk 是个长字符串手抄或者复制的时候容易漏字符或者被文本编辑器当成特殊字符处理。建议从控制台复制之后直接粘贴别经过聊天工具或者文档转手中间带上了不可见字符找起来特别费劲。5.3 上线前的自检清单项目上线前我一般会过一遍下面这些检查项前端和服务端各申请了对应类型的 key没有混用浏览器端 key 的白名单里包含了生产域名和必要的测试域名服务端 key 的 IP 白名单里包含了所有可能发起请求的服务器出口 IP瓦片地址使用的是 HTTPS关键层级范围在服务支持范围内有瓦片缓存机制避免重复请求吃满配额控制台设置了调用量告警接近上限时能收到通知降级方案就绪万一天地图侧抖动前端能切换备用底图这套清单执行下来能挡掉绝大多数 403 问题。真遇到过不了的用前面那套从浏览器到命令行的流程走一遍基本都能定位到具体环节。最后分享一个小技巧如果排查过程中怀疑是密钥本身的问题不去动生产环境的配置用控制台里另建一个测试应用配上同样的参数试试。两个 key 的表现一对比立马就能判断出是密钥配置问题还是环境问题。这个隔离测试的思路我用了好多年省下来的时间比什么都值。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →