Merge pull request #745 from szmadd/fix/oidc-cross-system-login

fix(oidc): 修复跨系统OIDC登录后回到本系统仍需重新登录的问题
This commit is contained in:
Wenjie Zhang 2026-06-09 12:36:00 +08:00 committed by GitHub
commit 159bec21d6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 103 additions and 7 deletions

View File

@ -3,6 +3,7 @@ import AppLayout from '@/layouts/AppLayout.vue'
import BlankLayout from '@/layouts/BlankLayout.vue'
import { useUserStore } from '@/stores/user'
import { useAgentStore } from '@/stores/agent'
import { sanitizeRedirect } from '@/utils/oidcAutoStart'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
@ -207,9 +208,9 @@ router.beforeEach(async (to) => {
}
}
// 如果用户已登录但访问登录页
// 如果用户已登录但访问登录页,按 redirect 参数跳转
if (to.path === '/login' && isLoggedIn) {
return '/'
return sanitizeRedirect(to.query.redirect)
}
// 其他情况正常导航

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

View File

@ -283,6 +283,7 @@ import {
Key as KeyIcon,
AlertCircle as ExclamationCircleIcon
} from 'lucide-vue-next'
import { tryAutoStartOIDC, sanitizeRedirect } from '@/utils/oidcAutoStart'
const router = useRouter()
const route = useRoute()
@ -539,9 +540,11 @@ const checkOIDCConfig = async () => {
if (config.provider_name) {
oidcButtonText.value = config.provider_name
}
return config
} catch (error) {
console.error('检查 OIDC 配置失败:', error)
oidcEnabled.value = false
return null
} finally {
oidcChecking.value = false
}
@ -614,9 +617,9 @@ const checkServerHealth = async () => {
//
onMounted(async () => {
//
// redirect
if (userStore.isLoggedIn) {
router.push('/')
router.push(sanitizeRedirect(route.query.redirect))
return
}
@ -631,8 +634,21 @@ onMounted(async () => {
//
await checkFirstRunStatus()
// OIDC
checkOIDCConfig()
// OIDC
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
}
})
//

View File

@ -32,6 +32,7 @@ import { useUserStore } from '@/stores/user'
import { useAgentStore } from '@/stores/agent'
import { authApi } from '@/apis/auth_api'
import { message } from 'ant-design-vue'
import { clearAutoStartAttempt } from '@/utils/oidcAutoStart'
const router = useRouter()
const route = useRoute()
@ -84,9 +85,10 @@ const handleCallback = async () => {
//
message.success('登录成功')
//
// OIDC
const redirectPath = sessionStorage.getItem('oidc_redirect') || '/'
sessionStorage.removeItem('oidc_redirect')
clearAutoStartAttempt()
loading.value = false