用户认证(Auth Callback)
文档:
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