Merge pull request #745 from szmadd/fix/oidc-cross-system-login
fix(oidc): 修复跨系统OIDC登录后回到本系统仍需重新登录的问题
This commit is contained in:
commit
159bec21d6
@ -3,6 +3,7 @@ import AppLayout from '@/layouts/AppLayout.vue'
|
|||||||
import BlankLayout from '@/layouts/BlankLayout.vue'
|
import BlankLayout from '@/layouts/BlankLayout.vue'
|
||||||
import { useUserStore } from '@/stores/user'
|
import { useUserStore } from '@/stores/user'
|
||||||
import { useAgentStore } from '@/stores/agent'
|
import { useAgentStore } from '@/stores/agent'
|
||||||
|
import { sanitizeRedirect } from '@/utils/oidcAutoStart'
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
history: createWebHistory(import.meta.env.BASE_URL),
|
history: createWebHistory(import.meta.env.BASE_URL),
|
||||||
@ -207,9 +208,9 @@ router.beforeEach(async (to) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果用户已登录但访问登录页
|
// 如果用户已登录但访问登录页,按 redirect 参数跳转
|
||||||
if (to.path === '/login' && isLoggedIn) {
|
if (to.path === '/login' && isLoggedIn) {
|
||||||
return '/'
|
return sanitizeRedirect(to.query.redirect)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 其他情况正常导航
|
// 其他情况正常导航
|
||||||
|
|||||||
77
web/src/utils/oidcAutoStart.js
Normal file
77
web/src/utils/oidcAutoStart.js
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
const AUTO_START_KEY = 'oidc_auto_start_attempted'
|
||||||
|
|
||||||
|
// 对 redirect 参数进行安全校验,防止开放重定向漏洞
|
||||||
|
// - 必须是字符串类型
|
||||||
|
// - 必须以 / 开头(本地相对路径)
|
||||||
|
// - 不能以 // 或 \ 开头(协议相对 URL / 非标准路径)
|
||||||
|
// 不合法或缺失时返回 "/"
|
||||||
|
export function sanitizeRedirect(value) {
|
||||||
|
if (typeof value === 'string' && value.length > 0 && value[0] === '/' && value[1] !== '/' && value[1] !== '\\') {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
return '/'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否已经尝试过自动触发 OIDC(同一会话内,避免无限循环)
|
||||||
|
export function hasAutoStartAttempted() {
|
||||||
|
return sessionStorage.getItem(AUTO_START_KEY) === '1'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 标记已尝试自动触发
|
||||||
|
export function markAutoStartAttempted() {
|
||||||
|
sessionStorage.setItem(AUTO_START_KEY, '1')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清除自动触发尝试标记(OIDC 登录成功后调用)
|
||||||
|
export function clearAutoStartAttempt() {
|
||||||
|
sessionStorage.removeItem(AUTO_START_KEY)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 尝试自动触发 OIDC 登录
|
||||||
|
// config: OIDC 配置对象(由调用方在外部获取)
|
||||||
|
// getOIDCLoginUrl: 获取登录 URL 的异步函数
|
||||||
|
// 返回 true 表示已发起跳转,caller 不应继续执行后续流程
|
||||||
|
export async function tryAutoStartOIDC(getOIDCLoginUrl, config) {
|
||||||
|
// 1. OIDC 配置未就绪或未启用
|
||||||
|
if (!config || !config.enabled) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 有 oidc_error 时不再自动触发,避免循环
|
||||||
|
const params = new URLSearchParams(window.location.search)
|
||||||
|
if (params.has('oidc_error')) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 必须有 autostartOidc 参数
|
||||||
|
if (!params.has('autostartOidc')) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 同一会话内已尝试过,不再重复
|
||||||
|
if (hasAutoStartAttempted()) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. 获取 OIDC 登录 URL 并跳转
|
||||||
|
let loginUrlResp
|
||||||
|
try {
|
||||||
|
loginUrlResp = await getOIDCLoginUrl()
|
||||||
|
} catch {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!loginUrlResp || !loginUrlResp.login_url) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存当前路径,登录后返回(安全校验)
|
||||||
|
const redirectPath = sanitizeRedirect(params.get('redirect'))
|
||||||
|
sessionStorage.setItem('oidc_redirect', redirectPath)
|
||||||
|
|
||||||
|
// 标记已尝试,防止下次再自动触发
|
||||||
|
markAutoStartAttempted()
|
||||||
|
|
||||||
|
window.location.href = loginUrlResp.login_url
|
||||||
|
return true
|
||||||
|
}
|
||||||
@ -283,6 +283,7 @@ import {
|
|||||||
Key as KeyIcon,
|
Key as KeyIcon,
|
||||||
AlertCircle as ExclamationCircleIcon
|
AlertCircle as ExclamationCircleIcon
|
||||||
} from 'lucide-vue-next'
|
} from 'lucide-vue-next'
|
||||||
|
import { tryAutoStartOIDC, sanitizeRedirect } from '@/utils/oidcAutoStart'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
@ -539,9 +540,11 @@ const checkOIDCConfig = async () => {
|
|||||||
if (config.provider_name) {
|
if (config.provider_name) {
|
||||||
oidcButtonText.value = config.provider_name
|
oidcButtonText.value = config.provider_name
|
||||||
}
|
}
|
||||||
|
return config
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('检查 OIDC 配置失败:', error)
|
console.error('检查 OIDC 配置失败:', error)
|
||||||
oidcEnabled.value = false
|
oidcEnabled.value = false
|
||||||
|
return null
|
||||||
} finally {
|
} finally {
|
||||||
oidcChecking.value = false
|
oidcChecking.value = false
|
||||||
}
|
}
|
||||||
@ -614,9 +617,9 @@ const checkServerHealth = async () => {
|
|||||||
|
|
||||||
// 组件挂载时
|
// 组件挂载时
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
// 如果已登录,跳转到首页
|
// 如果已登录,按 redirect 参数跳转(不固定跳首页)
|
||||||
if (userStore.isLoggedIn) {
|
if (userStore.isLoggedIn) {
|
||||||
router.push('/')
|
router.push(sanitizeRedirect(route.query.redirect))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -631,8 +634,21 @@ onMounted(async () => {
|
|||||||
// 检查是否是首次运行
|
// 检查是否是首次运行
|
||||||
await checkFirstRunStatus()
|
await checkFirstRunStatus()
|
||||||
|
|
||||||
// 检查 OIDC 配置
|
// 如果处于首次运行状态,不需要 OIDC 自动登录
|
||||||
checkOIDCConfig()
|
if (isFirstRun.value) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查 OIDC 配置完成后,尝试自动触发 OIDC 登录(跨系统跳转场景)
|
||||||
|
const config = await checkOIDCConfig()
|
||||||
|
if (config && config.enabled) {
|
||||||
|
const autoStarted = await tryAutoStartOIDC(
|
||||||
|
async () => await authApi.getOIDCLoginUrl(),
|
||||||
|
config
|
||||||
|
)
|
||||||
|
// 如果已发起 OIDC 跳转,页面会被重定向,不需要继续
|
||||||
|
if (autoStarted) return
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// 组件卸载时清理定时器
|
// 组件卸载时清理定时器
|
||||||
|
|||||||
@ -32,6 +32,7 @@ import { useUserStore } from '@/stores/user'
|
|||||||
import { useAgentStore } from '@/stores/agent'
|
import { useAgentStore } from '@/stores/agent'
|
||||||
import { authApi } from '@/apis/auth_api'
|
import { authApi } from '@/apis/auth_api'
|
||||||
import { message } from 'ant-design-vue'
|
import { message } from 'ant-design-vue'
|
||||||
|
import { clearAutoStartAttempt } from '@/utils/oidcAutoStart'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
@ -84,9 +85,10 @@ const handleCallback = async () => {
|
|||||||
// 显示成功消息
|
// 显示成功消息
|
||||||
message.success('登录成功')
|
message.success('登录成功')
|
||||||
|
|
||||||
// 获取重定向路径
|
// 获取重定向路径并清理 OIDC 相关标记
|
||||||
const redirectPath = sessionStorage.getItem('oidc_redirect') || '/'
|
const redirectPath = sessionStorage.getItem('oidc_redirect') || '/'
|
||||||
sessionStorage.removeItem('oidc_redirect')
|
sessionStorage.removeItem('oidc_redirect')
|
||||||
|
clearAutoStartAttempt()
|
||||||
|
|
||||||
loading.value = false
|
loading.value = false
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user