Skip to content

访问控制 ​

访问受保护的服务需要 API Key。这两个示例演示带 Key 访问的两种方式,以及访问失败时如何区分原因。

在沙箱里请使用未限制来源域名的 Key

沙箱的运行页出于安全考虑运行在隔离的环境里,它发出的请求不带可识别的来源域名。设置了来源域名白名单的 Key 在沙箱里一定会被拒绝(错误码 TOKEN_INVALID)。如需测试带白名单的 Key,请在沙箱里点「下载」,把得到的 HTML 放到白名单内的站点上运行,详见在线沙箱。

用 API Key 访问受保护服务 ​

在公开的地图方案底图上叠加一个需要 API Key 的服务。默认的请求头方式:transformRequest 给发往平台的请求加上 X-GeoBridge-Token 请求头,底图与叠加层、要素查询都带 Key。另一种是地址参数方式:用 client.withKey() 把 Key 作为 token 参数补进地址,适合不能自定义请求头的库;打印日志时用 GeoBridge.maskToken() 遮蔽 Key。左上角的面板逐行显示 Key 是否填写、这个 Key 可访问的服务数、叠加的图层数,以及用要素查询统计的铁路车站数。

需要: 一个访问级别为"需要 API Key"的服务,以及对它有访问权限的 API Key(形如 gb_xxx…)。运行前先在沙箱页面顶部填写 Key。

js
/**
 * @title 用 API Key 访问受保护服务
 * @description 在地图方案底图上叠加需要 API Key 的交通设施服务:默认的 header 模式用请求头带 Key 出图,也演示 withKey 的 query 形态;面板显示 Key 状态、可访问的服务数与港铁站数。需要先在页面顶部填写 Key。
 * @category 访问控制
 * @service demo/protected
 * @map demo/map
 */
// 出错时给出可理解的提示:受保护服务没带 Key 或 Key 无效时,SDK 抛出带 code 的 GeoBridgeError
const friendly = (e) =>
  e && e.code === 'TOKEN_REQUIRED' ? '该服务需要 API Key:请在页面顶部填写 Key 后再运行'
    : e && e.code === 'TOKEN_INVALID' ? 'API Key 无效、已吊销,或限制了来源域名'
      : e && e.status === 404 ? '服务不存在,请检查服务路径' : e && e.message

// 面板:逐行显示检查结果
const panel = document.createElement('div')
panel.style.cssText = 'position:absolute;top:8px;left:8px;z-index:2;background:#fff;padding:6px 10px;border-radius:6px;box-shadow:0 1px 4px rgba(0,0,0,.3);font:13px/1.8 sans-serif;max-width:360px'
container.appendChild(panel)
const line = (text, level = 'log') => {
  panel.appendChild(document.createElement('div')).textContent = text
  console[level](text)
}

try {
  // 拷到自己页面时:换成你的受保护服务路径,创建客户端时用 apiKey 选项传入你的 Key
  const path = services['demo/protected']
  line(client.hasApiKey ? 'API Key:已填写' : 'API Key:未填写(请在页面顶部填写后再运行)')

  // header 模式(默认):transformRequest 对平台地址加请求头 X-GeoBridge-Token,底图与叠加层都经过它
  // 提示:平台写进样式的地址以平台配置的 webBaseUrl 为前缀;它与 baseUrl 不一致时 header 模式不会带 Key
  const map = new maplibregl.Map({
    container,
    style: client.maplibre.mapStyleUrl(maps['demo/map']),
    transformRequest: client.transformRequest,
    center: [114.172, 22.298],
    zoom: 15,
  })
  map.addControl(new maplibregl.NavigationControl())
  // 切片、图标等资源加载失败时 MapLibre 发 error 事件:转成提示,不影响其余内容显示
  map.on('error', (e) => console.warn('地图资源加载失败:', e.error && e.error.message))
  await new Promise((resolve) => map.once('load', resolve))

  // 1. 列出 Key 可访问的服务:目录接口不对匿名开放(沙箱运行页也不带平台的登录状态),没有 Key 时跳过
  if (client.hasApiKey) {
    const list = await client.services.list()
    line(`该 Key 可访问 ${list.length} 个服务`)
  } else {
    line('列出可访问服务需要 API Key:目录接口不对匿名开放')
  }

  // 2. 带 Key 叠加受保护的矢量瓦片服务,再放大服务样式里的圆点便于看清
  const overlay = await client.maplibre.addVectorTileService(map, path)
  for (const id of overlay.layerIds) if (map.getLayer(id).type === 'circle') map.setPaintProperty(id, 'circle-radius', 3)
  line(`已叠加受保护图层:${overlay.layerIds.length} 个`)

  // 3. 带 Key 查询要素服务:统计港铁站等铁路车站
  const stations = await client.query.count(path, 0, { where: "fclass = 'railway_station'" })
  line(`铁路车站:${stations} 个`)

  // 4. query 形态:withKey 给平台地址补 token 参数,适合不能加请求头的库;打印前用 maskToken 遮蔽
  console.log('query 形态的切片地址:', GeoBridge.maskToken(client.withKey(client.services.vectorTileUrl(path))))
  // 整张地图都走 query 形态时,把 transformRequest 换成:(url) => ({ url: client.withKey(url) })
  // 或直接创建 query 模式的客户端:new GeoBridge.GeoBridgeClient({ baseUrl, apiKey, keyTransport: 'query' })
} catch (e) {
  line('访问失败:' + friendly(e), 'error')
}
在沙箱中打开

错误处理 ​

分别用匿名客户端、格式正确但不存在的 Key、以及页面顶部填写的 Key 访问受保护服务。失败时 SDK 抛出 GeoBridgeError,按 code 区分原因并给出中文提示,结果以表格显示在地图上:

code含义
TOKEN_REQUIRED服务需要 API Key,请求没有带
TOKEN_INVALIDKey 无效、已吊销,或来源域名不在 Key 的白名单内
UNAUTHORIZED匿名访问不存在或仅限成员访问的服务;平台不透露服务是否存在
FORBIDDEN已登录,但没有该服务所在工作空间的访问权限(带 Key 的请求不会得到此码)
NETWORK、TIMEOUT网络不通或请求超时

前两次失败是故意演示的,示例用普通日志输出;浏览器的网络面板里仍会看到这两次请求返回 401。

需要: 一个访问级别为"需要 API Key"的服务;填写了 Key 时会多一次成功的访问。

js
/**
 * @title 错误处理
 * @description 分别用匿名客户端、无效 Key 与页面顶部填写的 Key 访问受保护服务:失败时 SDK 抛 GeoBridgeError,按 code 给出中文提示;结果以红绿表格显示在地图上。前两次失败是故意演示,以普通日志输出。
 * @category 访问控制
 * @service demo/protected
 * @map demo/map
 */
const path = services['demo/protected']

// 把 GeoBridgeError 的 code 翻译成给用户看的提示;拷到自己页面时可直接复用
function explain(e) {
  if (!(e instanceof GeoBridge.GeoBridgeError)) return `非预期错误:${e.message}`
  if (e.status === 404) return '服务不存在,请检查服务路径'
  switch (e.code) {
    case 'TOKEN_REQUIRED':
      return '该服务需要 API Key,请先申请并填写'
    case 'TOKEN_INVALID':
      return 'API Key 无效、已吊销,或来源域名不在白名单内'
    case 'UNAUTHORIZED':
      // 匿名访问不存在或仅限成员的服务时,平台统一返回 401,不透露服务是否存在
      return '服务不存在,或仅限工作空间成员登录后访问'
    case 'FORBIDDEN':
      return '没有访问此服务的权限'
    case 'NETWORK':
    case 'TIMEOUT':
      return '网络不通或请求超时,请稍后重试'
    default:
      return `请求失败(${e.code},HTTP ${e.status})`
  }
}

// 底图用公开的地图方案,匿名也能显示
new maplibregl.Map({ container, style: client.maplibre.mapStyleUrl(maps['demo/map']), center: [114.172, 22.298], zoom: 15 })

const table = document.createElement('table')
table.style.cssText = 'position:absolute;top:8px;left:8px;z-index:2;background:#fff;border-collapse:collapse;box-shadow:0 1px 4px rgba(0,0,0,.3);font:13px/1.6 sans-serif'
container.appendChild(table)

async function attempt(label, c) {
  let ok = true
  let text = '访问成功'
  try {
    await c.services.get(path, 'VectorTileServer')
  } catch (e) {
    ok = false
    text = `${e.code}:${explain(e)}`
  }
  const row = table.insertRow()
  for (const [i, v] of [label, ok ? '✓' : '✗', text].entries()) {
    const cell = row.insertCell()
    cell.textContent = v
    cell.style.cssText = `padding:4px 10px;border-bottom:1px solid #eee;${i === 1 ? `color:${ok ? '#2f9e44' : '#e03131'};font-weight:bold` : ''}`
  }
  // 故意演示的失败也用 console.log 输出,不算示例自身出错
  console.log(label, '→', text)
}

// 匿名:没有 Key
await attempt('匿名客户端', new GeoBridge.GeoBridgeClient({ baseUrl: client.baseUrl }))
// 构造的无效 Key:格式正确但不存在
const invalidKey = 'gb_' + 'x'.repeat(43)
await attempt('无效 Key', new GeoBridge.GeoBridgeClient({ baseUrl: client.baseUrl, apiKey: invalidKey }))
// 页面顶部填写的 Key(沙箱提供的 client)
if (client.hasApiKey) await attempt('页面填写的 Key', client)
else console.log('页面顶部未填写 Key,跳过第三次尝试')
在沙箱中打开