如何在Vue3项目中实现微信授权登录

目录
文章目录隐藏
  1. 前言
  2. 准备工作
  3. 实现思路
  4. 上代码
  5. 总结

如何在 Vue3 项目中实现微信授权登录

前言

微信授权登录是做微信公众号开发一直绕不开的话题,而且整个授权登录流程的实现,是需要前后端配合一起完成的。在过去前后端还未分离的年代,也许我们前端并不需要太过关心授权的具体实现。然而现在都 2021 年了,前后端分离的架构大行其道,如何在前后端分离的情况下实现微信授权登录就成了今天要探讨的重点问题。

准备工作

首先,我们还是需要先梳理下微信授权整个流程是怎样的,这里我就直接将官方文档搬来:

如果用户在微信客户端中访问第三方网页,公众号可以通过微信网页授权机制,来获取用户基本信息,进而实现业务逻辑。

关于网页授权的两种 scope 的区别说明:

1、以 snsapi_base 为 scope 发起的网页授权,是用来获取进入页面的用户的 openid 的,并且是静默授权并自动跳转到回调页的。用户感知的就是直接进入了回调页(往往是业务页面);

2、以 snsapi_userinfo 为 scope 发起的网页授权,是用来获取用户的基本信息的。但这种授权需要用户手动同意,并且由于用户同意过,所以无须关注,就可在授权后获取该用户的基本信息。

具体而言,网页授权流程分为四步:

1、引导用户进入授权页面同意授权,获取code

2、通过code换取网页授权access_token(与基础支持中的access_token不同);

3、如果需要,开发者可以刷新网页授权access_token,避免过期;

4、通过网页授权access_tokenopenid获取用户基本信息(支持 UnionID 机制)。

这里附上微信公众号开发之微信授权的官方文档

以上是笔者提炼出来的比较关键的几点信息,当然还有更多的说明,希望新手读者们还是先认真看完官方文档。

这里我再补充说明下,以上流程的 4 个步骤中,除了第一步以外,另外三步都是需要在服务器端去完成的。前端要做的核心其实是怎么进行用户登录状态的检查判断和登录状态的维护。

实现思路

大家都知道,Vue 是前后端分离技术方案下的产物,它是一个纯前端应用(服务端渲染除外)。通常我们是需要在用户打开页面,执行到页面的 js 脚本时,我们再异步去请求服务端数据,再进行相关逻辑的处理和判断。我们要实现微信授权登录的前提是,需要先判断用户是否需要登录(cookie 或者 token)。当用户未登录时,才需要走授权登录流程,当授权登录成功后,我们也需要在前端记录好登录状态,以方便在页面切换时,不用再次触发授权登录。再通过分析可知,前端其实能做的就是获取微信服务器给我们的 code,再将 code 给我们的后端,让后端完成后续步骤拿到用户信息后生成用户。那么整个过程我再梳理如下:

  1. (前端)检查用户是否登录;
  2. (前端)如果未登录,引导用户进入授权页面同意授权,获取 code
  3. (前端)将获取到的 code 提交给后端
  4. (后端)通过 code 换取用户凭证 openid
  5. (后端)通过 openid 检查用户是否存在,是否需要注册新用户,并获取用户 id
  6. (后端)返回用户信息;
  7. (前端)记录用户登录状态,跳回登录前页面;

这个过程,我画了个图,如下:

如何在 Vue3 项目中实现微信授权登录

上代码

根据以上思路,现在开始编码环节。笔者采用的是 Vue3,Vue2 的开发者还请根据情况做适当调整。

为了方便唤起用户授权登录逻辑,笔者打算将授权登录封住为一个 login 页面,这样做的好处是我们在任何判断到需要登录的地方直接通过 Vue Router 的 push 方法跳转到登录页面即可。

通常情况下,我们的应用并不是所有页面都需要登录后才能访问,只有在访问特定页面时候,才需要用户登录,那么我们就需要标识哪些页面需要进行登录鉴权。这里我们可以利用 Vue Router 的 meta 属性来进行标识,官方文档对 meta 解释如下:

有时,你可能希望将任意信息附加到路由上,如过渡名称、谁可以访问路由等。这些事情可以通过接收属性对象的meta属性来实现,并且它可以在路由地址和导航守卫上都被访问到。

刚好 Vue Router 官方就有示例,如下:

const routes = [
  {
    path: '/posts',
    component: PostsLayout,
    children: [
      {
        path: 'new',
        component: PostsNew,
        // 需要登录后才能访问的页面
        meta: { requiresAuth: true }
      },
      {
        path: ':id',
        component: PostsDetail,
        // 任何人都可访问的页面
        meta: { requiresAuth: false }
      }
    ]
  }
]

接下来我们就可以在 Vue Router 的全局守卫beforeEach中获取到这个元信息从而做登录跳转了。

router.beforeEach((to, from) => {
  // 而不是去检查每条路由记录
  // to.matched.some(record => record.meta.requiresAuth)
  if (to.meta.requiresAuth && !userStore.isLogin) {
    // 此路由需要授权,请检查是否已登录
    // 如果没有,则重定向到登录页面
    return {
      path: '/login',
      // 保存我们所在的位置,以便以后再来
      query: { redirect: to.fullPath },
    }
  }
})

需要补充说明的是,userStore.isLogin的实现。这里和我们实际采用的登录态维护方案有关,如果是采用token方式的话,那就是检查token是否已经存在。笔者采用了vuex作为来保存token,然后借助插件来将Store中的数据持久化到localStorage

接下来我们来看具体的实现:

login.vue: 登录组件

<template>
  <div class="login"></div>
</template>

<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>

可以看到,login 页面其实并没有什么内容,跳转到该页面后,我们会直接重定向到微信授权的页面,授权回调回来也会回到该页面,此时我们再通过获取路由参数的方式获取 code 参数。

@/hooks/usePage.ts: 该文件主要是封装了 router 相关的常用方法。

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/useWechatAuth.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/modules/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-persistedstate插件将store中数据存储到了localStorage,这样做的好处是用户关闭页面后,再次访问,即不用重新触发微信授权流程,大大优化了用户体验。

总结

不得不说,Vue3 在代码抽象和复用这块上,写起来着实舒服很多,希望大家也也尝试着按照官方实践那样,多将逻辑代码解耦抽离,生成一个个的 hook,这样代码就显得优雅多啦。该方案经过笔者尝试论证,不论是代码整洁优雅程度,还是业务需求的实现上,都几乎完美(请容我装一波 b)。当然,这里可能存在我没发现的 bug 或者痛点,毕竟从来没有十全十美的架构嘛,这里也欢迎看官大佬们和我交流探讨,提供更好的方案和想法。

「点点赞赏,手留余香」

3

给作者打赏,鼓励TA抓紧创作!

微信微信 支付宝支付宝

还没有人赞赏,快来当第一个赞赏的人吧!

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。
码云笔记 » 如何在Vue3项目中实现微信授权登录

1 评论

  1. 我刚完成是react版本的微信授权登录,哈哈

发表回复