阶段四:模拟微信 OAuth 登录 从零构建带AI流式对话的Vue2全栈应用-CSDN博客本文是对这篇文章阶段四的讲解一、为什么不能只用一个“登录成功”按钮真正的第三方登录不是前端自己决定登录成功。需要经过你的前端 微信授权服务器 你的后端基本流程用户点击微信登录 ↓ 跳转微信授权页 ↓ 微信返回临时 code ↓ 前端把 code 交给后端 ↓ 后端验证 code ↓ 后端建立自己系统的登录状态阶段四没有真实微信资质所以使用本地页面模拟微信。二、OAuth 中的几个重要概念1.codecode是一次性临时授权码。它不是最终登录 Token。特点有效时间短 通常只能使用一次 由微信授权后返回 应该交给后端验证2.statestate是前端登录前生成的随机字符串。登录前保存sessionStorage.setItem( wechat_oauth_state, state )回调时比较returnedState expectedState它的作用类似取号票。登录前前端取一张号码票 登录回来检查是不是同一张票如果不一致说明这个回调可能不是当前登录操作产生的。3. Token后端验证code后生成自己的 Token。Token 表示这个浏览器已经登录为哪个用户。阶段四中 Token 放在 Vuex 内存中。三、后端为什么使用Mapconst 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?codexxxstatexxx前端才能通过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十、阶段四 Vuexstate: { authToken: null, currentUser: null }登录前authToken null currentUser null登录后this.$store.commit( setAuth, result )触发 mutationsetAuth(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 只保存在 VuexauthToken: nullVuex 是网页内存。按 F5 后页面重新加载 Vue 应用重新创建 Vuex 重新创建 authToken 恢复 null 路由守卫判断未登录 跳转登录页这不是错误而是阶段四设计上的限制。阶段五会解决这个问题。