前言
微信授權登錄是微信公眾號開發(fā)中繞不開的話題,整個授權登錄流程的實現(xiàn)需要前后端的配合。以前前后端不分離的時候微信狼王授權碼注冊機,也許我們的前端不需要太關心授權的具體實現(xiàn)。不過現(xiàn)在是2021年,前后端分離的架構非常流行。如何在前后端分離的情況下實現(xiàn)微信授權登錄成為今天要討論的重點問題。
準備
首先,我們還是要梳理一下微信授權的全流程。這里我直接搬官方文檔:
如果用戶在微信客戶端訪問第三方網(wǎng)頁,公眾號可以通過微信網(wǎng)頁授權機制獲取用戶的基本信息,進而實現(xiàn)業(yè)務邏輯。
…
關于兩種網(wǎng)頁授權范圍的區(qū)別:
1、scope發(fā)起的網(wǎng)頁授權,用于獲取進入頁面的用戶,靜默授權,自動跳轉到回調頁面。用戶感知到的是直接進入回調頁面(通常是業(yè)務頁面);
2、scope發(fā)起的網(wǎng)頁授權,用于獲取用戶的基本信息。但該授權需要用戶手動同意,且由于用戶已同意,授權后無需關注即可獲取用戶的基本信息。
…
具體來說,網(wǎng)頁授權過程分為四個步驟:
1、引導用戶進入授權頁面同意授權并獲取密碼;
2、網(wǎng)頁授權交換碼(不同于基礎支持);
3、如有需要,開發(fā)者可以刷新網(wǎng)頁授權,避免過期;
4、通過網(wǎng)頁授權和獲取用戶基本信息(支持機制)。
附上微信授權微信公眾號開發(fā)的官方文檔。
以上是作者提取的一些比較關鍵的信息。當然,還有更多的解釋。希望新手讀者先仔細閱讀官方文檔。
這里我補充一下,在上述流程的四個步驟中,除了第一步之外,其他三個步驟都需要在服務器端完成。前端的核心其實就是如何檢查判斷用戶的登錄狀態(tài),維護登錄狀態(tài)。
實現(xiàn)想法
眾所周知,Vue是前后端分離技術解決方案的產(chǎn)物。它是一個純前端應用程序(服務器端渲染除外)。通常,當用戶打開頁面并執(zhí)行頁面的js腳本時,我們會異步請求服務端數(shù)據(jù),然后對相關邏輯進行處理和判斷。實現(xiàn)微信授權登錄的前提是我們需要先判斷用戶是否需要登錄(或token)。當用戶未登錄時,需要經(jīng)過授權登錄過程。當授權登錄成功時,我們還需要在前端記錄登錄狀態(tài),這樣當頁面切換時,就不需要再次觸發(fā)授權登錄了。通過分析,我們可以看到,前端實際上可以做的是獲取微信服務器給我們的代碼Okdo Image to Jpeg J2k Jp2 Pcx Converter(圖片轉換工具),然后將代碼發(fā)送到我們的后端,這樣后端就可以完成后續(xù)的步驟來獲取用戶信息和生成用戶。那么我將整個過程整理如下:
(前端)檢查用戶是否登錄;(前端)若未登錄,引導用戶進入授權頁面同意授權,獲取代碼(前端)將獲取的代碼提交給后端(后端)兌換代碼為用戶憑證(后端)通過檢查用戶是否存在,是否需要注冊新用戶,獲取用戶id(后端)返回用戶信息;(前端)記錄用戶的登錄狀態(tài),并跳轉回預登錄頁面;
這個過程,我畫了一張圖Picture Information Extractor(圖片信息提取工具),如下:
上面的代碼
根據(jù)以上思路,現(xiàn)在開始編碼環(huán)節(jié)。筆者使用Vue3,Vue2開發(fā)者請根據(jù)情況進行適當調整。
為了方便用戶授權登錄邏輯,作者擬將授權登錄封裝為登錄頁面。這樣做的好處是,無論我們在哪里判斷需要登錄,都可以通過Vue的push方法直接跳轉到登錄頁面。
通常,并非我們應用程序的所有頁面都需要登錄才能訪問。只有當特定頁面被訪問時,用戶才需要登錄。然后我們需要識別哪些頁面需要登錄認證。這里我們可以使用Vue的meta屬性來識別picFormat(圖片格式轉換器),官方文檔對meta的解釋如下:
有時,你可能想在路由上附加任意信息,例如轉換名稱、誰可以訪問該路由等。這些事情可以通過接收 對象的元屬性來實現(xiàn),并且可以在兩個路由上訪問地址和導航守衛(wèi)。
正好Vue官方有一個例子,如下:
const routes = [ { path: '/posts', component: PostsLayout, children: [ {path: 'new', component: PostsNew, // 需要登錄后才能訪問的頁面 meta: { requiresAuth: true } }, { path: ':id', component: PostsDetail, // 任何人都可訪問的頁面 meta: { requiresAuth: false } } ] } ]
接下來,我們可以在Vue的全局守衛(wèi)中獲取這個元信息來進行登錄跳轉。
router.beforeEach((to, from) => { // 而不是去檢查每條路由記錄 // to.matched.some(record => record.meta.requiresAuth) if (to.meta.requiresAuth && !userStore.isLogin) { // 此路由需要授權,請檢查是否已登錄 // 如果沒有,則重定向到登錄頁面 return { path: '/login', // 保存我們所在的位置,以便以后再來 query: { redirect: to.fullPath }, } } })
應該補充的是,執(zhí)行 . 這和我們實際使用的登錄狀態(tài)維護方案有關。如果使用token方法,就是檢查token是否已經(jīng)存在。作者使用vuex保存token,然后使用插件將Store中的數(shù)據(jù)持久化到。
接下來,我們來看看具體的實現(xiàn):
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,則已經(jīng)授權 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>
可以看出微信狼王授權碼注冊機,登錄頁面實際上并沒有任何內(nèi)容。跳轉到這個頁面后,我們會直接跳轉到微信授權的頁面,授權回調也會返回到這個頁面。這時候我們會通過Get code參數(shù)的方式來獲取路由參數(shù)。
@/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參數(shù) */ 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:全局狀態(tài)存儲,主要是登錄前記錄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時,表示創(chuàng)建動態(tài)模塊,運行時將模塊注冊到存儲中 // preserveState 如果數(shù)據(jù)有持久化,該變量為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- 將數(shù)據(jù)存儲在 store 中。這樣做的好處是用戶關閉頁面后可以再次訪問,即無需重新觸發(fā)微信授權流程,大大優(yōu)化了用戶體驗。
總結
不得不說,Vue3 在代碼抽象和重用方面寫起來真的很舒服。希望大家也盡量按照官方的做法對邏輯代碼進行解耦和分離,并一一生成hook,讓代碼看起來優(yōu)雅很多。經(jīng)過筆者嘗試演示,該方案無論從代碼的簡潔優(yōu)雅,還是業(yè)務需求的實現(xiàn)上,都已經(jīng)接近完美(請允許我裝一波b)。當然,可能有我在這里沒有發(fā)現(xiàn)的錯誤或痛點。畢竟,從來沒有完美的架構。在這里,歡迎您和我一起討論,提供更好的解決方案和想法。