第 6 章:用户认证系统
“在互联网上,没人知道你是一条狗,除非你必须要登录。”
用户认证(Authentication)是所有应用的第一道防线。本章我们将深入探讨如何结合微信小程序生态,构建一套安全、无感的身份认证系统。
6.1 微信登录流程详解
在传统 Web 开发中,我们通常使用“账号 + 密码”登录。但在小程序里,这种方式既麻烦又不安全。我们利用微信提供的 login 能力,实现“一键登录”。
标准流程图 (The Flow)
sequenceDiagram
participant U as 用户 (User)
participant C as 小程序端 (Client)
participant S as 云函数 (Cloud Function)
participant DB as 云数据库 (DB)
U->>C: 点击“一键登录”
C->>S: 调用 auth/login
Note over C,S: 云开发原生支持!\n无需 wx.login 获取 code\n云函数自动注入 OPENID
S->>S: 获取 cloud.getWXContext()
S->>DB: 查询 users 表 (where openid=xxx)
alt 用户不存在
S->>DB: 创建新用户 (Role='user')
else 用户已存在
S->>DB: 更新最后登录时间
end
S->>S: 生成 JWT Token
S-->>C: 返回 Token + UserInfo
C->>C: 存入 Pinia & Storage
关键点:
- OpenID 自动注入: 只要在小程序端调用云函数,腾讯云网关会自动在
cloud.getWXContext()中注入当前用户的 OpenID。完全不需要前端折腾uni.login获取code再去换取 SessionKey,省去了一大堆麻烦。 - 免密: 用户不需要输入密码,体验极佳。
6.2 JWT Token 生成与验证
虽然云函数调用自带 OpenID,但为了统一管理登录态(特别是未来如果扩展 H5 端),我们实现了一套 JWT (JSON Web Token) 机制。
6.2.1 为什么需要 JWT?
- 无状态: 服务器不需要存储 Session,Token 本身包含了身份信息。
- 角色管理: 我们将
role(user/admin) 签入 Token,前端拿到 Token 解析后即可知道用户权限,无需频繁查库。
6.2.2 生成 Token (Sign)
在 server/auth/index.js 中:
const token = jwt.sign(
{
userId: user._id,
openid: user.openid,
role: user.role // 核心:把权限写进 Token
},
process.env.JWT_SECRET, // 密钥,千万不能泄露!
{ expiresIn: '10d' } // 有效期 10 天
)
6.2.3 验证 Token (Verify)
我们在所有业务云函数(如 activity, vote)的入口处都有一个拦截器:
// 通用验证逻辑
const verifyJWT = (token) => {
try {
const decoded = jwt.verify(token, JWT_SECRET)
return { valid: true, userId: decoded.userId, role: decoded.role }
} catch (err) {
return { valid: false, error: 'Token 过期' }
}
}
6.3 角色权限设计
我们在 users 表中设计了 role 字段:
user: 普通用户。只能浏览活动、报名、投票、查看自己的记录。admin: 普通管理员。只能管理自己创建的活动(编辑、下架、审核报名)。super_admin: 超级管理员。系统的神。可以管理所有活动,可以设置某个用户为管理员,查看全平台数据。
越权防御: 即使前端黑客修改了 UI 显示出“删除按钮”,点击调用云函数时:
// server/activity/index.js
// 场景:删除活动
if (userRole !== 'super_admin' && activity.creatorId !== currentUserId) {
return { code: 403, message: '大胆!竟敢动别人的活动!' }
}
后端校验是安全的最后一道防线,永远不要相信前端传来的数据。
6.4 前端鉴权拦截器
在前端 src/utils/request.ts 中,我们封装了统一的请求逻辑:
- 请求拦截: 自动在 header 或 data 中携带
token。 - 响应拦截:
- 如果返回
code === 401(未登录/过期),自动跳转到登录页,并清除本地缓存。 - 如果返回
code === 403(无权限),弹出提示“权限不足”。
- 如果返回
// 伪代码示例
const request = async (options) => {
const userStore = useUserStore()
const token = userStore.token
const res = await uniCloud.callFunction({
name: 'server-api',
data: { ...options.data, token } // 自动注入 Token
})
if (res.result.code === 401) {
userStore.logout()
uni.navigateTo({ url: '/pages/login/index' })
return Promise.reject('Unauthorized')
}
return res.result
}
本章小结: 我们利用微信生态实现了便捷的登录,又通过 JWT 实现了灵活的权限控制。这套“微信 OpenID + JWT”的组合拳,既保证了安全性,又兼顾了用户体验。下一章,我们将开始构建活动管理的核心功能——活动的 CRUD。