前言
微信授权登录是微信公众号开发中绕不开的话题,整个授权登录流程的实现需要前后端的配合。以前前后端不分离的时候微信狼王授权码注册机,也许我们的前端不需要太关心授权的具体实现。不过现在是2021年,前后端分离的架构非常流行。如何在前后端分离的情况下实现微信授权登录成为今天要讨论的重点问题。
准备
首先,我们还是要梳理一下微信授权的全流程。这里我直接搬官方文档:
如果用户在微信客户端访问第三方网页,公众号可以通过微信网页授权机制获取用户的基本信息,进而实现业务逻辑。
…
关于两种网页授权范围的区别:
1、scope发起的网页授权,用于获取进入页面的用户,静默授权,自动跳转到回调页面。用户感知到的是直接进入回调页面(通常是业务页面);
2、scope发起的网页授权,用于获取用户的基本信息。但该授权需要用户手动同意,且由于用户已同意,授权后无需关注即可获取用户的基本信息。
…
具体来说,网页授权过程分为四个步骤:
1、引导用户进入授权页面同意授权并获取密码;
2、网页授权交换码(不同于基础支持);
3、如有需要,开发者可以刷新网页授权,避免过期;
4、通过网页授权和获取用户基本信息(支持机制)。
附上微信授权微信公众号开发的官方文档。
以上是作者提取的一些比较关键的信息。当然,还有更多的解释。希望新手读者先仔细阅读官方文档。
这里我补充一下,在上述流程的四个步骤中,除了第一步之外,其他三个步骤都需要在服务器端完成。前端的核心其实就是如何检查判断用户的登录状态,维护登录状态。
实现想法
众所周知,Vue是前后端分离技术解决方案的产物。它是一个纯前端应用程序(服务器端渲染除外)。通常,当用户打开页面并执行页面的js脚本时,我们会异步请求服务端数据,然后对相关逻辑进行处理和判断。实现微信授权登录的前提是我们需要先判断用户是否需要登录(或token)。当用户未登录时,需要经过授权登录过程。当授权登录成功时,我们还需要在前端记录登录状态,这样当页面切换时,就不需要再次触发授权登录了。通过分析,我们可以看到,前端实际上可以做的是获取微信服务器给我们的代码Okdo Image to Jpeg J2k Jp2 Pcx Converter(图片转换工具),然后将代码发送到我们的后端,这样后端就可以完成后续的步骤来获取用户信息和生成用户。那么我将整个过程整理如下:
(前端)检查用户是否登录;(前端)若未登录,引导用户进入授权页面同意授权,获取代码(前端)将获取的代码提交给后端(后端)兑换代码为用户凭证(后端)通过检查用户是否存在,是否需要注册新用户,获取用户id(后端)返回用户信息;(前端)记录用户的登录状态,并跳转回预登录页面;
这个过程,我画了一张图Picture Information Extractor(图片信息提取工具),如下:
上面的代码
根据以上思路,现在开始编码环节。笔者使用Vue3,Vue2开发者请根据情况进行适当调整。
为了方便用户授权登录逻辑,作者拟将授权登录封装为登录页面。这样做的好处是,无论我们在哪里判断需要登录,都可以通过Vue的push方法直接跳转到登录页面。
通常,并非我们应用程序的所有页面都需要登录才能访问。只有当特定页面被访问时,用户才需要登录。然后我们需要识别哪些页面需要登录认证。这里我们可以使用Vue的meta属性来识别picFormat(图片格式转换器),官方文档对meta的解释如下:
有时,你可能想在路由上附加任意信息,例如转换名称、谁可以访问该路由等。这些事情可以通过接收 对象的元属性来实现,并且可以在两个路由上访问地址和导航守卫。
正好Vue官方有一个例子,如下:
const routes = [ { path: '/posts', component: PostsLayout, children: [ { path: 'new', component: PostsNew, // 需要登录后才能访问的页面 meta: { requiresAuth: true } }, { path: ':id', component: PostsDetail, // 任何人都可访问的页面 meta: { requiresAuth: false } } ] } ]
接下来,我们可以在Vue的全局守卫中获取这个元信息来进行登录跳转。
router.beforeEach((to, from) => { // 而不是去检查每条路由记录 // to.matched.some(record => record.meta.requiresAuth) if (to.meta.requiresAuth && !userStore.isLogin) { // 此路由需要授权,请检查是否已登录 // 如果没有,则重定向到登录页面 return { path: '/login', // 保存我们所在的位置,以便以后再来 query: { redirect: to.fullPath }, } } })
应该补充的是,执行 . 这和我们实际使用的登录状态维护方案有关。如果使用token方法,就是检查token是否已经存在。作者使用vuex保存token,然后使用插件将Store中的数据持久化到。
接下来,我们来看看具体的实现:
login.vue:登录组件
<script lang="ts"> import { defineComponent } from 'vue' import { jump2Auth, getUserInfo } from '@/hooks/useWechatAuth' import { userStore } from '@/store/modules/user' import { redirectTo, getRouteQuery } from '@/hooks/usePage' export default defineComponent({ name: 'Login', setup() { let code = getRouteQuery().code as string // 3.如果有code,则已经授权 if (code) { getUserInfo(code as string).then((res: any) => { // 记录token userStore.saveToken(res.access_token) const redirect = userStore.userState.landPageRoute || '/' // 跳转到授权前访问的页面 redirectTo(redirect) }) } else { // 1.记录上一个页面的地址 const { redirect } = getRouteQuery() if (redirect) { userStore.setLandPage(redirect as string) } // 2.跳转授权 const callbackUrl = window.location.origin + window.location.pathname jump2Auth(callbackUrl) } }, }) </script>
可以看出微信狼王授权码注册机,登录页面实际上并没有任何内容。跳转到这个页面后,我们会直接跳转到微信授权的页面,授权回调也会返回到这个页面。这时候我们会通过Get code参数的方式来获取路由参数。
@/hooks/.ts:这个文件主要封装了相关的常用方法。
import router from '@/router' import { cloneDeep } from 'lodash' import { toRaw } from 'vue' /** * 重定向 * @param path 路径 */ export function redirectTo(path: string) { const { replace } = router replace({ path, }) } /** * 获取路由上query参数 */ export function getRouteQuery() { const { currentRoute } = router const { query } = currentRoute.value return cloneDeep(query) }
@/hooks/.ts:该文件封装了微信授权与后端交互的请求。
import { useAxios } from '@/hooks/useAxios' /** * 获取微信授权的跳转地址 * @param callbackUrl 授权后回调链接 * @returns */ export function jump2Auth(callbackUrl: string) { useAxios({ url: '/api/wechat/auth', params: { redirect_url: callbackUrl, }, }).then((authUrl: any) => { if (process.env.NODE_ENV === 'development') { window.location.href = callbackUrl + '?code=test' } else { window.location.href = authUrl } }) } /** * 提交code进行登录 * @param code * @returns */ export async function getUserInfo(code: string) { const userInfo = await useAxios({ method: 'POST', url: '/api/wechat/auth', params: { code, }, }) return userInfo }
@/store//user.ts:全局状态存储,主要是登录前记录token和访问页面。
import { Module, VuexModule, Mutation, getModule, Action } from 'vuex-module-decorators' import store from '@/store' import { initialUnencryptedStorage } from '../globals' interface UserState { token: string landPageRoute: string } const NAME = 'user' // name: 模块名字 // namespaced 表示开启命名空间 // dynamic设置为true时,表示创建动态模块,运行时将模块注册到存储中 // preserveState 如果数据有持久化,该变量为true时可以从storage中拿取初始值 @Module({ namespaced: true, name: NAME, dynamic: true, store, preserveState: Boolean(initialUnencryptedStorage[NAME]), }) export class User extends VuexModule { userState: UserState = { token: '', /** 登录前访问页面 */ landPageRoute: '', } get isLogin(): boolean { return !!this.userState.token } @Mutation saveToken(token: string): void { this.userState.token = token } @Mutation setLandPage(route: string): void { this.userState.landPageRoute = route } } export const userStore = getModule(User)
作者使用 vuex- 将数据存储在 store 中。这样做的好处是用户关闭页面后可以再次访问,即无需重新触发微信授权流程,大大优化了用户体验。
总结
不得不说,Vue3 在代码抽象和重用方面写起来真的很舒服。希望大家也尽量按照官方的做法对逻辑代码进行解耦和分离,并一一生成hook,让代码看起来优雅很多。经过笔者尝试演示,该方案无论从代码的简洁优雅,还是业务需求的实现上,都已经接近完美(请允许我装一波b)。当然,可能有我在这里没有发现的错误或痛点。毕竟,从来没有完美的架构。在这里,欢迎您和我一起讨论,提供更好的解决方案和想法。
发表评论