fix(oidc): 修复跨系统OIDC登录后回到本系统仍需重新登录的问题
- LoginView 挂载时等待 OIDC 配置就绪后显式调用自动触发逻辑 - 新增 oidcAutoStart.js 工具模块,支持 autostartOidc 参数判断和 sessionStorage 防循环 - OIDC 回调成功后清理自动触发标记,避免影响下一次跨系统登录 - 已登录用户访问 /login 时按 redirect 参数跳转目标页,不再固定跳回首页 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
c2e755848e
commit
50969ed175
@ -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),
|
||||
@ -192,9 +193,9 @@ router.beforeEach(async (to, from, next) => {
|
||||
return
|
||||
}
|
||||
|
||||
// 如果用户已登录但访问登录页
|
||||
// 如果用户已登录但访问登录页,按 redirect 参数跳转
|
||||
if (to.path === '/login' && isLoggedIn) {
|
||||
next('/')
|
||||
next(sanitizeRedirect(to.query.redirect))
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
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,
|
||||
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
|
||||
}
|
||||
})
|
||||
|
||||
// 组件卸载时清理定时器
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user