'use client' import { useEffect } from 'react' import { AppShell, Group, Text, Button, Avatar, Paper, Stack, Badge, Code, Divider, SimpleGrid, Menu, } from '@mantine/core' import { IconLogout, IconUser, IconShield, IconServer, IconKey, } from '@tabler/icons-react' interface UserInfo { id: string account: string name: string phone: string roles: string[] } // 构建跳转 sso-portal 的登录 URL(OAuth2:app_id 即 client_id) const SSO_APP_ID = process.env.NEXT_PUBLIC_SSO_APP_ID || '' const SSO_URL = (process.env.NEXT_PUBLIC_SSO_URL || 'http://localhost:5501').replace(/\/+$/, '') const APP_URL = (process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:4000').replace(/\/+$/, '') const SSO_STATE_STORAGE_KEY = 'sso_login_state' function buildSsoLoginUrl(): string { const bytes = new Uint8Array(32) window.crypto.getRandomValues(bytes) const state = Array.from(bytes, (byte) => byte.toString(16).padStart(2, '0')).join('') const loginUrl = new URL(`${SSO_URL}/login`) window.sessionStorage.setItem(SSO_STATE_STORAGE_KEY, state) loginUrl.searchParams.set('app_id', SSO_APP_ID) loginUrl.searchParams.set('app_url', `${APP_URL}/callback`) loginUrl.searchParams.set('state', state) return loginUrl.toString() } export default function DashboardPage({ user, authenticated, }: { user: UserInfo | null // 登录态以 token 为准(拿到 access_token 即算成功);user 可能为 null authenticated: boolean }) { // 未登录(无 token)-> 跳转 SSO useEffect(() => { if (!authenticated) { window.location.href = buildSsoLoginUrl() } }, [authenticated]) if (!authenticated) { return ( 正在跳转到 SSO 登录页面... ) } const handleLogout = async () => { await fetch('/api/logout', { method: 'POST' }) // 跳到 SSO 登录页;每次登录都生成新的 state 防止登录 CSRF。 window.location.href = buildSsoLoginUrl() } return ( 外部应用 Demo 已登录 {user?.name?.[0] || 'U'} {user?.name || '已登录用户'} }> {user?.account || '(未获取到账号)'} } color="red" onClick={handleLogout}> 退出登录 登录成功! 您已通过 SSO 认证登录到此应用(OAuth2 授权码流程,已换取 access_token)。 用户信息 {user ? ( ID {user.id} 账号 {user.account} 姓名 {user.name} 手机 {user.phone} ) : ( 后端 access_token 接口未返回用户详情,暂无用户信息。 )} 角色权限 {user?.roles?.length ? ( {user.roles.map((role: string) => ( {role} ))} ) : ( 暂无角色信息 )} SSO 流程说明 1. 您打开此应用(http://localhost:4000 2. 检测到未登录,自动跳转到 SSO(http://localhost:5501/login 3. 在 SSO 页面完成登录,SSO 返回 sso_code 4. 此应用用 sso_code 调用后端 /api/v1/basis/sso/access_token 换取 access_token 5. access_token 存入 httpOnly Cookie,即算登录成功;用户详情需后端 userinfo 接口提供 ) }