从零构建带AI流式对话的Vue2全栈应用-CSDN博客本文是对这篇文章阶段四的讲解
一、为什么不能只用一个“登录成功”按钮
真正的第三方登录不是前端自己决定登录成功。
需要经过:
你的前端 微信授权服务器 你的后端基本流程:
用户点击微信登录 ↓ 跳转微信授权页 ↓ 微信返回临时 code ↓ 前端把 code 交给后端 ↓ 后端验证 code ↓ 后端建立自己系统的登录状态阶段四没有真实微信资质,所以使用本地页面模拟微信。
二、OAuth 中的几个重要概念
1.code
code是一次性临时授权码。
它不是最终登录 Token。
特点:
有效时间短 通常只能使用一次 由微信授权后返回 应该交给后端验证2.state
state是前端登录前生成的随机字符串。
登录前保存:
sessionStorage.setItem( 'wechat_oauth_state', state )回调时比较:
returnedState === expectedState它的作用类似取号票。
登录前:前端取一张号码票 登录回来:检查是不是同一张票如果不一致,说明这个回调可能不是当前登录操作产生的。
3. Token
后端验证code后,生成自己的 Token。
Token 表示:
这个浏览器已经登录为哪个用户。
阶段四中 Token 放在 Vuex 内存中。
三、后端为什么使用Map
const authorizationCodes = new Map() const loginTokens = new Map()Map是 JavaScript 的键值集合。
可以理解成字典。
保存授权码:
authorizationCodes.set(code, { user: mockWechatUser, expiresAt: ... })读取授权码:
authorizationCodes.get(code)删除授权码:
authorizationCodes.delete(code)登录 Token 也是一样:
Token → 当前用户四、生成随机字符串
function createRandomString(byteLength = 24) { return crypto .randomBytes(byteLength) .toString('hex') }crypto.randomBytes()用于生成安全随机数据。
不能简单地使用:
Math.random()生成真正的登录 Token,因为安全性不够。
生成结果类似:
83a6ce8f019d7d8d...五、获取授权地址接口
前端请求:
GET /api/auth/wechat/authorize-url携带:
redirectUri state后端返回:
{ authorizeUrl: 'http://localhost:3000/mock-wechat/authorize?...' }前端执行:
window.location.href = result.authorizeUrl这不是 Vue 路由跳转,而是整个浏览器跳转到新的网页地址。
六、模拟微信授权页面
后端使用:
res.type('html').send(` <!DOCTYPE html> <html> ... </html> `)返回一整个 HTML 页面。
浏览器访问:
http://localhost:3000/mock-wechat/authorize时,不显示 JSON,而是显示绿色授权页面。
点击同意后,访问:
/mock-wechat/approve后端生成code,再重定向回前端。
七、重定向回 Hash 路由
前端使用的是 Hash 路由:
http://localhost:8081/#/oauth/callback需要把参数放在#后面的路由中:
http://localhost:8081/#/oauth/callback?code=xxx&state=xxx前端才能通过:
this.$route.query.code this.$route.query.state读取。
八、一次性授权码
后端生成:
authorizationCodes.set(code, { user: mockWechatUser, expiresAt: Date.now() + 5 * 60 * 1000 })五分钟有效。
换取登录状态时:
authorizationCodes.delete(code)先删除,再使用。
这表示同一个code第二次使用会失败。
九、阶段四鉴权中间件
function requireAuth(req, res, next) { const authorization = req.headers.authorization || '' if ( !authorization.startsWith( 'Bearer ' ) ) { return res.status(401).json({ code: 401, message: '请先登录' }) } const token = authorization.slice( 'Bearer '.length ) const currentUser = loginTokens.get(token) if (!currentUser) { return res.status(401).json({ code: 401, message: '登录状态无效' }) } req.currentUser = currentUser req.currentToken = token next() }next()是什么
Express 中间件处理完以后,需要调用:
next()才能继续执行真正的接口。
流程:
请求 /api/users ↓ 先执行 requireAuth ↓ Token 无效:直接返回 401 Token 有效:执行 next() ↓ 继续执行用户接口Bearer Token 格式
前端请求头:
Authorization: Bearer abc123后端先检查:
startsWith('Bearer ')再去掉前面的文字:
authorization.slice('Bearer '.length)最终得到:
abc123十、阶段四 Vuex
state: { authToken: null, currentUser: null }登录前:
authToken = null currentUser = null登录后:
this.$store.commit( 'setAuth', result )触发 mutation:
setAuth(state, payload) { state.authToken = payload.token state.currentUser = payload.user }为什么 mutation 不能随便省略
推荐的数据修改流程:
组件 ↓ commit mutation ↓ 修改 state而不是在组件中到处写:
this.$store.state.authToken = token使用 mutation 能让数据变化更清楚,也方便调试。
十一、Axios 自动添加 Token
请求拦截器:
const token = store.state.authToken if (token) { config.headers.Authorization = `Bearer ${token}` }登录后每次发送 Axios 请求都会自动带上:
Authorization: Bearer ...所以页面调用:
getUsers()时,不需要自己再写 Token。
十二、路由守卫
router.beforeEach((to, from, next) => { const requiresAuth = to.matched.some(record => { return record.meta.requiresAuth }) const isLoggedIn = store.getters.isLoggedIn if ( requiresAuth && !isLoggedIn ) { next('/login') return } next() })to
将要进入的页面。
from
当前离开的页面。
next
决定下一步去哪里。
next()正常进入目标页面。
next('/login')改为进入登录页。
to.matched.some
一个页面可能匹配父路由和子路由。
to.matched.some(...)检查所有匹配的路由中,有没有:
meta.requiresAuth === true有就说明页面需要登录。
十三、阶段四为什么刷新后会退出
阶段四 Token 只保存在 Vuex:
authToken: nullVuex 是网页内存。
按 F5 后:
页面重新加载 Vue 应用重新创建 Vuex 重新创建 authToken 恢复 null 路由守卫判断未登录 跳转登录页这不是错误,而是阶段四设计上的限制。
阶段五会解决这个问题。