> **本文回答**：请求 403 / 401 / CORS —— token 配置错、无权限、前置代理丢了跨域头。

## 一、症状

### 403 Forbidden（token 问题看这里）

```
GET https://vjmap.com/server/api/v1/map/openmap/xxx 403 (Forbidden)
// body: {"code":7,"data":{"reload":true},"msg":"Token is invalid"}
```

token 缺失/无效/过期都由网关 JWT 中间件返回 **403**（`msg` 为 `Token is invalid` / `Token expired` 等），不是 401。

### 401 Unauthorized

```
GET .../map/openmap/xxx 401 (Unauthorized)
```

vjmap 服务只在**服务未激活授权 / 超出最大用户数**时返回 401（body `{error:...}`），与 token 无关。

### CORS blocked

```
Access to fetch at 'https://xxx.com/api/...' from origin 'https://yyy.com'
has been blocked by CORS policy
```

## 二、token 错误（403，body.code=7）

### 原因

- token 未设
- token 过期
- token 对应的账号没权限
- 前端硬编码 token（被泄漏后封禁）

### 排查

```js
const svc = new vjmap.Service(
  'https://vjmap.com/server/api/v1/', // url
  'xxxxxxx'                            // token
);
```

### Node 环境（自定义 IRequest）的 403 陷阱

浏览器版 `Service` 内部用 `XMLHttpRequest`，且 **token 走 `args.headers.token` 请求头，不在 query 参数里**。在 Node 里测 SDK 纯接口时必须：① `globalThis.window = globalThis`（UMD 顶层引用 window）；② 注入 fetch 版 `IRequest`（`new Service(url, token, req)` 第三参），且**必须转发 `args.headers`**，否则所有请求静默 403 `{code:7, msg:"Token is invalid"}`、SDK 层只看到 `{reload:true}`：

```js
globalThis.window = globalThis;
const req = {
  get: async (url, params, args) => {
    const u = new URL(url);
    for (const [k, v] of Object.entries(params ?? {})) u.searchParams.set(k, v);
    const r = await fetch(u, { headers: args?.headers });  // ← token 在这里
    const t = await r.text();
    try { return JSON.parse(t); } catch { return t; }
  },
  post: async (url, data, args) => {
    const r = await fetch(url, { method: "POST", headers: args?.headers, body: JSON.stringify(data) });
    return r.json();
  },
  put: async () => ({}), patch: async () => ({}), del: async () => ({}), options: async () => ({}),
};
const svc = new vjmap.Service(url, token, req);  // 第三参注入
```

排查特征：SDK 返回 `{reload: true}` 但 `currentMapParam()` 里 `bounds`/`version` 全是 null → 多半是所有请求都在 403。




## 三、403 权限不足（body 为 `{error:"no permission to access this map…"}`）

### 原因

- token 账号没有该地图的访问权限
- 密码图没带 secretKey/accessKey（`openMap` 是 HTTP 200 + `{status:403, error}` 带内返回；其他命令是 HTTP 403）
- 地图被设为"仅自己可见"
- 共享过期

### 排查

1. 登录 vjmap 后台 → 我的地图 → 找到对应 mapid
2. 看"访问权限"设置
3. 或用管理员 token 测试：能行 → 是权限问题

## 四、CORS 错误

### 原因

- 前端域名 `https://app.com` 请求后端 `https://api.other.com`，且中间某层没带 `Access-Control-Allow-Origin`
- vjmap 服务（官方与自建）**默认已放行 `*`**：地图服务每个响应都带 `Access-Control-Allow-Origin: *`，`/api/v1/service` 组走 cors 中间件（AllowOrigins `*`）——**没有 CORS 配置项可开关**
- 所以自建部署出现 CORS 报错，多是前置 Nginx/网关未透传、改写了头，或 OPTIONS 预检被拦

### 前置 Nginx 时补头

如果前置代理吞了上游的跨域头，在 Nginx 反向代理加头：

```nginx
add_header Access-Control-Allow-Origin $http_origin;
add_header Access-Control-Allow-Credentials true;
add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
add_header Access-Control-Allow-Headers 'Content-Type, Authorization, token';   # SDK 用 token 请求头

if ($request_method = OPTIONS) {
  return 204;
}
```

### 前端代理（开发）

Vite `vite.config.js`：

```js
export default {
  server: {
    proxy: {
      '/vjmap-api': {
        target: 'https://vjmap.com',
        changeOrigin: true,
        rewrite: p => p.replace(/^\/vjmap-api/, '/server/api/v1')
      }
    }
  }
};
```

```js
const svc = new vjmap.Service('/vjmap-api/', token);   // 走代理，绕 CORS
```

## 五、混合内容（Mixed Content）

```
Blocked loading mixed active content
```

### 原因

- 前端 HTTPS 页面调 HTTP 后端 → 被浏览器拦

### 修法

- 后端升级 HTTPS
- 或前端也用 HTTP（仅测试用）
- 或配置 `Content-Security-Policy: upgrade-insecure-requests`

## 六、WebSocket 握手失败

vjmap 某些后端特性走 WebSocket：

```
WebSocket connection to 'wss://...' failed
```

原因：

- 后端未开 WSS
- Nginx 未代理 `Upgrade` 头

Nginx：

```nginx
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
```

## 七、CORS 的 preflight

浏览器对 `PUT / DELETE` 或带自定义 header 的请求先发 OPTIONS：

```
Access-Control-Allow-Headers: Authorization, Content-Type
```

后端必须响应 OPTIONS 并返回允许的头。

## 八、排查一步到位

```js
try {
  const res = await svc.openMap({ mapid: 'xxx' });
  console.log(res);
  if (res.error) console.error('业务错误(HTTP 200 带内):', res.error);
} catch (err) {
  // SDK 走 XMLHttpRequest：非 2xx 一律 reject，err = {status, response, data, xhr}（不是 Error/TypeError）
  //   err.status === 0 → CORS / 网络 / URL 错
  //   err.status === 403 → JSON.parse(err.response).msg 为 "Token is invalid" 等 → token 问题；.error 为 no permission → 权限
  //   err.status === 404 → 图不存在；500 → 后端问题
  console.error('请求失败:', err.status, err.response);
}
```

结合 Network 面板看响应头：

- `Access-Control-Allow-Origin: *` 存在 → CORS OK
- 403 + `{code:7}` → token 问题；403 + `{error:"no permission…"}` → 权限
- 401 → 服务未激活 / 超最大用户数
- 500 → 后端问题

## 九、自建后端的常见配置缺失

| 缺 | 症状 | 修 |
|---|---|---|
| `Access-Control-Allow-Origin` | CORS blocked | Nginx / 后端加 |
| `Authorization` 头未放行 | preflight 失败 | `Access-Control-Allow-Headers: Authorization` |
| HTTPS 证书 | 混合内容 | 证书升级 |
| WebSocket 升级头 | 实时查询失败 | Nginx upgrade 配置 |

## 十、常见坑

1. **前端写死 token 上 git**：泄露后被封；改用后端签发。
2. **token 有尾部空格**：复制粘贴时带上了 → 403 `Token is invalid`；trim 一下。
3. **URL 末尾 `/` 无需手工处理**：`new vjmap.Service(url)` 会自动补末尾 `/`。
4. **Cookie 鉴权 + CORS 不带 credentials**：设 `credentials: 'include'` + 后端 `Access-Control-Allow-Credentials: true`。
5. **IP 白名单限制**：开发环境 IP 变了被拒；后台加白名单或用 token。

## 相关

- 安装 → [setup/install](../setup/install.md)
- Service → [backend/service-basics](../backend/service-basics.md)
- 打开图 → [open-map/open-dwg](../open-map/open-dwg.md)
