一、前言:为什么选择 TSRPC 做登录鉴权?
- TSRPC 核心优势:类型安全、前后端一体化、无冗余编码
- 登录鉴权场景适配性:Token 机制与 TSRPC 接口拦截的契合度
二、前期准备:环境搭建与基础配置
-
- 技术栈选型说明
- 前端:Vue
- 后端:Node.js + TSRPC
- 数据库:MongoDB(存储用户信息与 Token)
- 依赖:jsonwebtoken(生成 Token)
-
- TSRPC 前后端初始化
- 服务端项目创建与接口基础配置
- 前端项目创建与 TSRPC 客户端连接
- 前后端类型同步(TSRPC 核心特性,确保数据格式一致)
-
- 数据库表设计
- 用户表:存储用户名、密码等基础信息
- Token 表:存储用户 ID、Token 字符串、过期时间等
三、核心实现:登录流程全解析
- 3.1 第一步:前端登录表单实现与数据提交
- 登录表单组件开发(用户名、密码输入框 + 提交按钮)
- 表单验证:非空校验、格式合法性校验
- TSRPC 客户端调用登录接口(apiLogin),提交用户名密码
- 3.2 第二步:后端 apiLogin 接口实现(免鉴权配置)
- TSRPC 接口定义:请求参数类型(用户名、密码)与响应类型(Token)
- 免鉴权配置:通过 TSRPC 中间件排除 apiLogin 接口,无需 Token 即可访问
- 后端校验逻辑,查询数据库,验证用户名是否存在,验证密码是否正确
- 3.3 第三步:Token 生成与数据库存储
- 基于 jsonwebtoken 生成 Token:传入用户 ID 等核心信息,设置过期时间
- Token 存储到数据库:关联用户 ID,记录生成时间与过期时间
- 后端响应结果:将生成的 Token 返回给前端
四、关键拓展:全局接口鉴权实现
-
4.1 前端全局请求拦截
-
响应拦截
TypeScriptconst TokenStorageKey = 'tsrpc-token' // 响应拦截器 client.flows.preApiReturnFlow.push((v) => { if (v.return.isSucc) { if (v.return.res.__ssoToken) { localStorage.setItem(TokenStorageKey, JSON.stringify(v.return.res.__ssoToken)) } } else { const errorMsg = v.return.err.message // 解析错误码 if (errorMsg.startsWith('NEED_LOGIN:')) { ElMessage.error('请先登录') router.push('/login') } else { ElMessage.error(errorMsg.replace(/^[\w_]+:\s*/, '')) // 移除错误码前缀 } } return v }) -
请求拦截
TypeScriptclient.flows.preCallApiFlow.push((v) => { let tokenStr = localStorage.getItem(TokenStorageKey) v.req.__ssoToken = tokenStr ? JSON.parse(tokenStr) : undefined return v })
-
-
4.2 后端全局鉴权中间件:验证 Token 合法性
-
拦截器截获接口中带的 __ssoToken
TypeScript/** * 在 API 调用前解析当前用户身份 * @param server HttpServer */ export function parseCurrentUser(server: HttpServer) { // API 调用前的拦截器 server.flows.preApiCallFlow.push(async (call) => { let req = call.req as BaseRequest if (req.__ssoToken) { // 验证并解析 SSO Token const user = await UserService.verifyToken(req.__ssoToken) if (user) { call.currentUser = user } } return call }) } -
验证是用户身份
```TypeScript /** * 验证用户身份 * @param server HttpServer */ export function enableAuthentication(server: HttpServer) { // API 调用前的拦截器 server.flows.preApiCallFlow.push((call) => { let conf: BaseConf | undefined = call.service.conf // 默认需要登录,除非明确设置 needLogin: false if (conf?.needLogin !== false && !call.currentUser) { call.error('NEED_LOGIN: 请先登录') return undefined } return call }) } ``` -
校验结果处理:通过则放行,不通过则
call.error
-
五、补充功能:Token 注销与过期处理
-
- 退出登录功能:前端清除本地 Token + 后端删除数据库中对应 Token 记录
-
- Token 过期处理:
- 前端:拦截器中检测到以
NEED_LOGIN:开头的消息,跳转到登录页
TypeScriptconst errorMsg = v.return.err.message // 解析错误码 if (errorMsg.startsWith('NEED_LOGIN:')) { ElMessage.error('请先登录') router.push('/login') } else { ElMessage.error(errorMsg.replace(/^[\w_]+:\s*/, '')) // 移除错误码前缀 } -
- 数据库基本设计,两个表,token 和 user 两个表
TypeScriptexport interface db_Token { _id?: ObjectId userId: string token: string createdAt?: Date expiresAt?: Date }TypeScriptexport interface db_User { _id: ObjectId | string; username: string; // 用户名 password: string; // 密码 thumb?: string; // 头像 state: number; // 账户状态 updatedAt?: Date | string createdAt?: Date | string }
六:部分截图

