唯杰地图 logo唯杰地图
VJMAP
EN

用户认证(Auth Callback)

vjcad/references/version/user-authentication.md 8.3 KB207 行
原文

文档:vjcad/doc/version/user-auth.md 示例:vjcad/example/version/06userauthcallback.js

1. 总体架构

flowchart LR
    Front[MainView<br/>userInfo + onAuthError] -->|API 请求<br/>+ userId/sessionId| Cad[vjcad 后端]
    Cad -->|auth_callback HTTP| Auth[业务系统鉴权]
    Auth -->|allow / deny + reason| Cad
    Cad -->|返回结果或 401/403| Front

vjcad 后端不内建权限模型,而是把 当前用户 + 操作 + 资源 转发给 业务系统配置的 auth_callback,由业务系统决定放行还是拒绝。

2. 前端:MainView 配置

const cadView = new MainView({
    serviceUrl:  "http://127.0.0.1:27660/api/v1",
    accessToken: "your-access-token",

    userInfo: {
        userId:    "user_12345",
        userName:  "张三",
        sessionId: "sess_abc123",
        // 只认 userId / userName / sessionId 三个字段(存入 SystemConstants.USER_ID/USER_NAME/SESSION_ID);
        // 自定义字段(如 role)会被丢弃,不会发给后端——角色等信息请由业务系统按 userId/sessionId 自行查
    },

    onAuthError: (error) => {
        // error: { errorCode, message, operation } —— 字段是 errorCode 不是 code
        // 注意:v1.0.16 库内不会自动调用此回调(见 §4 末尾警示),此处为预留接线
        console.error("权限不足:", error);
        if (error.errorCode === 'AUTH_EXPIRED') location.href = '/login';
        else message.error(error.message || '没有权限执行该操作');
    },
});

initCadContainer('cad', cadView);   // 第一参是容器(id 或元素),第二参是 cadView
await cadView.onLoad();

完整代码:vjcad/example/version/06userauthcallback.js

后续登录态变化(切换用户 / 续签):

cadView.updateUserInfo({
    userId:    "user_67890",
    userName:  "李四",
    sessionId: "sess_new_xyz"
});

3. 后端:config.json

{
    "auth_callback": {
        "url":     "http://your-backend/api/cad/auth-check",
        "method":  "POST",
        "timeout": 5000,
        "fail_policy": "deny"        // "deny" | "allow",回调失败时的兜底
    }
}

fail_policy 推荐 deny:业务系统不通时禁止操作,安全优先。

4. 回调请求示意

vjcad 后端会以以下结构 POST 到业务系统:

{
    "userId":    "user_12345",
    "sessionId": "sess_abc123",
    "userName":  "张三",
    "operation": "saveWebcadPatch",
    "resource": {
        "type":        "imports",
        "mapId":       "test",
        "version":     "v1",
        "designPath":  "(仅 designs 类型)",
        "branch":      "main"
    }
}

⚠️ 字段名注意:resource 里是 mapId(驼峰,不是 mapid)和 branch(不是 branchName);顶层还含 userName。method: "GET" 时参数改走 query string(userId/sessionId/operation/mapId/version)。

业务系统返回 JSON,后端只解析三个字段:

{
    "allowed": true
}

或拒绝(reason/message 会被后端透传为 API 响应的 errorCode/error 字段):

{
    "allowed": false,
    "reason":  "no_permission",
    "message": "你没有该图纸的写权限"
}

拒绝后后端给前端的响应是 { status: 403, error: message, errorCode: reason }——即 reason 落在各 API 结果的 errorCode 上,message 落在 error 上。

⚠️ 这里的 status 是 数字 403(真值),而库里普遍用 if (!res.status) / if (res.status) 判成败(如 listBranches、SaveToServerCommand),被拒绝的结果会被当成成功(listBranches 拿到空数据,SAVESERVER 甚至提示保存成功)。自己调用时用 res.status === true 判成功,或先检查 res.errorCode:

const res = await drawingManager.saveDrawing({ /* ... */ });
if (res.errorCode) {                       // 鉴权拒绝(status 为 403)
    cadView.getOnAuthError()?.({ errorCode: res.errorCode, message: res.error || '', operation: 'saveWebcadPatch' });
    return;
}
if (res.status !== true) { /* 冲突或其它失败:看 res.error / res.conflict */ return; }

各类失败对应的 errorCode:

  • 200 + allowed:false → 响应 errorCode = reason(缺省 "no_permission"),error = message
  • 非 200 响应 → errorCode: "service_error"
  • 网络错误 / 超时 → errorCode: "service_unavailable";响应非 JSON → "service_error"(fail_policy: "allow" 时以上三类失败全部放行)

⚠️ onAuthError 在 vjcad@1.0.16 是死回调:MainViewConfig.onAuthError 会被存到 MainView._onAuthError 并经 getOnAuthError() 暴露,但库内没有任何代码调用它(types.d.ts 有声明、UMD 中存在)。要拿到权限拒绝需自行检查各 API 结果的 errorCode/status,或在自己的拦截层里调用 cadView.getOnAuthError()?.({ errorCode, message, operation })。

5. 操作枚举(operation)

后端 checkAuthCallback 实际只拦截这 7 个操作(不是蛇形命名):

operation 触发时机
listWebcadDraws 列图纸
getWebcadData 打开图纸——但 1.0.16 前端用二进制 GET 请求它、不带 userId,后端因此直接放行,打开图纸实际 不经过 auth_callback(仅未指定 patchId 时前置的 listWebcadDraws 会被鉴权)
saveWebcadPatch 保存补丁(SAVESERVER / saveDrawing)
deleteWebcadDraw 删除图纸 / 补丁
createWebcadBranch 创建分支
deleteWebcadBranch 删除分支
mergeWebcadBranch 合并分支

符号库读写、本地 IndexedDB 操作不经过 auth_callback;未配置 auth_callback 或请求未带 userId 时后端直接放行。前端只在走 Service.execCommand 的请求里自动注入 userId/userName/sessionId(且仅当配置了 userInfo.userId),getWebcadData 不走这条路——要保护"打开图纸",需在网关 / 反向代理层另做校验。

业务系统按 operation + resource 决策。

6. Node.js 回调骨架

// Express 示例
app.post('/api/cad/auth-check', async (req, res) => {
    const { userId, sessionId, operation, resource } = req.body;

    const session = await getSession(sessionId);
    if (!session || session.userId !== userId) {
        // 字段必须是 allowed / reason / message
        return res.json({ allowed: false, reason: 'session_expired', message: '会话失效' });
    }

    const ok = await checkPermission(userId, operation, resource);
    if (!ok) return res.json({ allowed: false, reason: 'no_permission', message: '没有权限' });

    res.json({ allowed: true });
});

7. 前端常见错误处理

error.errorCode 的值 = 业务返回的 reason 字段(自定义任意字符串)+ 后端内建值(若走 onAuthError 接线需自行在 API 结果检查处触发,见 §4):

onAuthError: (error) => {
    switch (error.errorCode) {
        case 'session_expired':          // 业务 reason(示例值,可自定义)
            location.href = '/login';
            break;
        case 'no_permission':            // reason 默认值
            message.error('权限不足');
            break;
        case 'service_unavailable':      // 后端内建:回调网络失败
        case 'service_error':            // 后端内建:回调非 200 / 响应非 JSON
            message.error('权限服务异常');
            break;
        default:
            message.error(error.message || '认证失败');
    }
}

红旗

  • userInfo.sessionId 必须 每次会话变更都更新(登录、续签、切租户)
  • 不要把 accessToken 直接当成用户身份;vjcad 用 userInfo 字段配合后端鉴权
  • 后端 auth_callback.timeout 不要太长(默认 5s 已足够),否则操作明显卡顿
  • 默认 fail_policy 必须是 deny;只有内网/POC 环境可放宽
  • 回调要做幂等:vjcad 在网络重试时可能重复请求同一 operation

参考(官方文档 MCP)

  • 文档:vjcad/doc/version/user-auth.md
  • 示例:vjcad/example/version/06userauthcallback.js