From 0003b0f1fd7c8061d5edd7c86afa2ccb9827a449 Mon Sep 17 00:00:00 2001 From: Sam <315859133@qq.com> Date: Fri, 6 Feb 2026 01:14:11 +0800 Subject: [PATCH] feat: implement OAuth 2.0 login flow with auto-redirect - Modify login page to auto-redirect to auth center - Update auth store to use OAuth login flow - Handle OAuth callback and token exchange - Update logout to use OAuth logout endpoint --- apps/web-ele/src/store/auth.ts | 109 ++++++++++++---- .../src/views/_core/authentication/login.vue | 119 +++++------------- .../src/views/fengling/oauth-callback.vue | 7 +- 3 files changed, 117 insertions(+), 118 deletions(-) diff --git a/apps/web-ele/src/store/auth.ts b/apps/web-ele/src/store/auth.ts index 74fadfe..0055039 100644 --- a/apps/web-ele/src/store/auth.ts +++ b/apps/web-ele/src/store/auth.ts @@ -10,7 +10,8 @@ import { resetAllStores, useAccessStore, useUserStore } from '@vben/stores'; import { ElNotification } from 'element-plus'; import { defineStore } from 'pinia'; -import { getAccessCodesApi, getUserInfoApi, loginApi, logoutApi } from '#/api'; +import { getAccessCodesApi, getUserInfoApi, logoutApi, oauthLogoutApi } from '#/api'; +import { oauthService } from '#/services/auth/oauth'; import { $t } from '#/locales'; export const useAuthStore = defineStore('auth', () => { @@ -21,53 +22,107 @@ export const useAuthStore = defineStore('auth', () => { const loginLoading = ref(false); /** - * 异步处理登录操作 - * Asynchronously handle the login process - * @param params 登录表单数据 + * OAuth 2.0 登录 - 重定向到认证中心 */ - async function authLogin( - params: Recordable, + function authLogin( + params?: Recordable, onSuccess?: () => Promise | void, ) { - // 异步处理用户登录操作并获取 accessToken - let userInfo: null | UserInfo = null; + loginLoading.value = true; + try { + // OAuth 2.0 方式:重定向到认证中心 + oauthService.initiateAuthorization(); + } catch (error) { + ElNotification({ + message: $t('authentication.loginFailed'), + title: $t('authentication.loginFailedTitle'), + type: 'error', + }); + } finally { + loginLoading.value = false; + } + } + + /** + * 处理 OAuth 回调 + */ + async function handleOAuthCallback(code: string, state: string) { try { loginLoading.value = true; - const { accessToken } = await loginApi(params); - // 如果成功获取到 accessToken + await oauthService.handleCallback(code, state); + + const accessToken = oauthService.getAccessToken(); if (accessToken) { - // 将 accessToken 存储到 accessStore 中 accessStore.setAccessToken(accessToken); - // 获取用户信息并存储到 accessStore 中 const [fetchUserInfoResult, accessCodes] = await Promise.all([ fetchUserInfo(), getAccessCodesApi(), ]); - userInfo = fetchUserInfoResult; - - userStore.setUserInfo(userInfo); + userStore.setUserInfo(fetchUserInfoResult); accessStore.setAccessCodes(accessCodes); - if (accessStore.loginExpired) { - accessStore.setLoginExpired(false); - } else { - onSuccess - ? await onSuccess?.() - : await router.push( - userInfo.homePath || preferences.app.defaultHomePath, - ); - } - - if (userInfo?.realName) { + if (fetchUserInfoResult?.realName) { ElNotification({ - message: `${$t('authentication.loginSuccessDesc')}:${userInfo?.realName}`, + message: `${$t('authentication.loginSuccessDesc')}:${fetchUserInfoResult?.realName}`, title: $t('authentication.loginSuccess'), type: 'success', }); } + + await router.push( + fetchUserInfoResult.homePath || preferences.app.defaultHomePath, + ); + } + } catch (error: any) { + ElNotification({ + message: error.message || $t('authentication.loginFailed'), + title: $t('authentication.loginFailedTitle'), + type: 'error', + }); + await router.replace(LOGIN_PATH); + } finally { + loginLoading.value = false; + } + } + + async function logout(redirect: boolean = true) { + try { + // OAuth 登出 + await oauthLogoutApi(); + } catch { + // 不做任何处理 + } + resetAllStores(); + accessStore.setLoginExpired(false); + + // OAuth 登出并重定向到认证中心 + oauthService.logout(); + } + + async function fetchUserInfo() { + let userInfo: null | UserInfo = null; + userInfo = await getUserInfoApi(); + userStore.setUserInfo(userInfo); + return userInfo; + } + + function $reset() { + loginLoading.value = false; + } + + return { + $reset, + authLogin, + fetchUserInfo, + handleOAuthCallback, + loginLoading, + logout, + }; +}); + } } } finally { loginLoading.value = false; diff --git a/apps/web-ele/src/views/_core/authentication/login.vue b/apps/web-ele/src/views/_core/authentication/login.vue index 099e4c8..fa9e249 100644 --- a/apps/web-ele/src/views/_core/authentication/login.vue +++ b/apps/web-ele/src/views/_core/authentication/login.vue @@ -1,98 +1,43 @@ diff --git a/apps/web-ele/src/views/fengling/oauth-callback.vue b/apps/web-ele/src/views/fengling/oauth-callback.vue index 0d1aedb..f5ca3db 100644 --- a/apps/web-ele/src/views/fengling/oauth-callback.vue +++ b/apps/web-ele/src/views/fengling/oauth-callback.vue @@ -2,10 +2,11 @@ import { onMounted, ref } from 'vue' import { useRouter, useRoute } from 'vue-router' import { ElMessage, ElCard, ElResult, ElButton } from 'element-plus' -import { oauthService } from '#/services/auth/oauth' +import { useAuthStore } from '#/store' const router = useRouter() const route = useRoute() +const authStore = useAuthStore() const loading = ref(true) const error = ref(null) @@ -18,9 +19,7 @@ onMounted(async () => { throw new Error('No authorization code found') } - await oauthService.handleCallback(code, state) - ElMessage.success('登录成功') - router.push('/') + await authStore.handleOAuthCallback(code, state) } catch (err: any) { error.value = err.message || '认证失败' ElMessage.error(error.value)