feat(auth): 统一身份 - 增加可选IAM快捷登录并保留原业务权限

This commit is contained in:
2026-09-10 10:33:03 +08:00
parent 27fbef9321
commit aa0d22bbe2
518 changed files with 5919 additions and 13 deletions
+78 -5
View File
@@ -11,14 +11,22 @@
<div class="text-center text-3xl font-medium mb-8">{{ config.web_name }}</div>
<!-- 企业微信自动授权中 -->
<div v-if="wxWorkAutoLogin" class="text-center py-10">
<div v-if="wxWorkAutoLogin || iamLoading" class="text-center py-10">
<el-icon class="is-loading mb-4" :size="40" color="var(--el-color-primary)">
<Loading />
</el-icon>
<div class="text-gray-500">企业微信授权登录中...</div>
<div class="text-gray-500">{{ iamLoading ? '统一账号登录中...' : '企业微信授权登录中...' }}</div>
</div>
<template v-else>
<el-button
v-if="iamEnabled"
class="mb-6"
size="large"
@click="handleIamLogin"
>
统一账号快捷登录
</el-button>
<!-- 登录方式切换标签 -->
<div v-if="wxWorkEnabled" class="flex justify-center mb-6">
<el-radio-group v-model="loginMode" size="large">
@@ -99,7 +107,7 @@ import LayoutFooter from '@/layout/components/footer.vue'
import useAppStore from '@/stores/modules/app'
import useUserStore from '@/stores/modules/user'
import cache from '@/utils/cache'
import { getWorkWechatConfig } from '@/api/user'
import { getIamConfig, getWorkWechatConfig } from '@/api/user'
const passwordRef = shallowRef<InputInstance>()
const formRef = shallowRef<FormInstance>()
@@ -118,6 +126,66 @@ const rules = {
password: [{ required: true, message: '请输入密码', trigger: ['blur'] }]
}
// 统一账号登录是可选入口,不取代账号密码或企业微信登录。
const iamEnabled = ref(false)
const iamLoginUrl = ref('')
const iamLoading = ref(false)
let iamCallbackHandled = false
const loadIamConfig = async () => {
try {
const result = await getIamConfig()
const url = new URL(result?.loginUrl || '', window.location.origin)
if (result?.enabled === true && url.protocol === 'https:') {
iamLoginUrl.value = url.href
iamEnabled.value = true
}
} catch {
// IAM 不可用时,原有登录入口保持可用。
}
}
const handleIamLogin = () => {
if (iamEnabled.value && iamLoginUrl.value) {
window.location.assign(iamLoginUrl.value)
}
}
const handleIamCallback = async (ticket: string | null, error: string | null) => {
if (iamCallbackHandled) return
iamCallbackHandled = true
// 在任何 await 和兑换前移除票据,刷新不会重复兑换;保留其他 query/hash。
const cleanUrl = new URL(window.location.href)
cleanUrl.searchParams.delete('iam_ticket')
cleanUrl.searchParams.delete('iam_error')
// 同一回调不得随后被识别为企业微信授权。
cleanUrl.searchParams.delete('code')
cleanUrl.searchParams.delete('state')
window.history.replaceState(window.history.state, '', cleanUrl.pathname + cleanUrl.search + cleanUrl.hash)
if (error || !ticket) {
ElMessage.error(error || '统一账号登录凭证无效,请重新登录')
return
}
iamLoading.value = true
try {
const result = await userStore.iamLogin(ticket)
if (result.is_paw === 0) {
await router.push('/change-password')
return
}
if (result.need_bind_work_wechat) {
await router.push('/bind-work-wechat')
return
}
redirectAfterLogin()
} catch (error: any) {
ElMessage.error(error?.msg || error?.message || '统一账号登录失败,请重新登录或使用账号密码')
loginMode.value = 'account'
} finally {
iamLoading.value = false
}
}
// 企业微信相关
const loginMode = ref<'account' | 'wxwork'>('account')
const wxWorkEnabled = ref(false)
@@ -243,7 +311,12 @@ onMounted(async () => {
// 检查 URL 中是否有企业微信回调 code
const urlParams = new URLSearchParams(window.location.search)
const wxCode = urlParams.get('code')
void loadIamConfig()
const hasIamCallback = urlParams.has('iam_ticket') || urlParams.has('iam_error')
if (hasIamCallback) {
await handleIamCallback(urlParams.get('iam_ticket'), urlParams.get('iam_error'))
}
const wxCode = hasIamCallback ? null : urlParams.get('code')
const wxState = urlParams.get('state')
if (wxCode && wxState === 'admin_login') {
@@ -263,7 +336,7 @@ onMounted(async () => {
wxWorkConfig.value = { corp_id: res.corp_id, agent_id: res.agent_id }
// 在企业微信内:自动跳转 OAuth 授权
if (isInWxWork()) {
if (isInWxWork() && !hasIamCallback) {
const redirectUri = encodeURIComponent(getRedirectUri())
const authUrl =
`https://open.weixin.qq.com/connect/oauth2/authorize` +