249 lines
8.3 KiB
TypeScript
249 lines
8.3 KiB
TypeScript
'use client';
|
||
|
||
import { useState } from 'react';
|
||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@repo/ui/components/card';
|
||
import { Button } from '@repo/ui/components/button';
|
||
import { Badge } from '@repo/ui/components/badge';
|
||
import { Alert, AlertDescription } from '@repo/ui/components/alert';
|
||
import { CheckCircle, XCircle, Loader2, ArrowRight, Key, User, Shield } from 'lucide-react';
|
||
|
||
export default function TestOidcPage() {
|
||
const [testResults, setTestResults] = useState<{
|
||
discovery: 'idle' | 'loading' | 'success' | 'error';
|
||
discoveryData?: any;
|
||
discoveryError?: string;
|
||
}>({
|
||
discovery: 'idle',
|
||
});
|
||
|
||
const testDiscoveryEndpoint = async () => {
|
||
setTestResults((prev) => ({ ...prev, discovery: 'loading' }));
|
||
|
||
try {
|
||
const response = await fetch('http://localhost:3000/oidc/.well-known/openid_configuration');
|
||
if (!response.ok) {
|
||
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||
}
|
||
|
||
const data = await response.json();
|
||
setTestResults((prev) => ({
|
||
...prev,
|
||
discovery: 'success',
|
||
discoveryData: data,
|
||
}));
|
||
} catch (error) {
|
||
setTestResults((prev) => ({
|
||
...prev,
|
||
discovery: 'error',
|
||
discoveryError: error instanceof Error ? error.message : '未知错误',
|
||
}));
|
||
}
|
||
};
|
||
|
||
const startOidcFlow = () => {
|
||
const params = new URLSearchParams({
|
||
response_type: 'code',
|
||
client_id: 'demo-client',
|
||
redirect_uri: 'http://localhost:3001/auth/callback',
|
||
scope: 'openid profile email',
|
||
state: `test-${Date.now()}`,
|
||
});
|
||
|
||
window.location.href = `http://localhost:3000/oidc/auth?${params.toString()}`;
|
||
};
|
||
|
||
const getStatusIcon = (status: string) => {
|
||
switch (status) {
|
||
case 'loading':
|
||
return <Loader2 className="h-5 w-5 animate-spin text-blue-500" />;
|
||
case 'success':
|
||
return <CheckCircle className="h-5 w-5 text-green-500" />;
|
||
case 'error':
|
||
return <XCircle className="h-5 w-5 text-red-500" />;
|
||
default:
|
||
return <div className="h-5 w-5 rounded-full border-2 border-gray-300" />;
|
||
}
|
||
};
|
||
|
||
return (
|
||
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100 dark:from-gray-900 dark:to-gray-800 p-8">
|
||
<div className="max-w-4xl mx-auto space-y-8">
|
||
{/* 页面标题 */}
|
||
<div className="text-center">
|
||
<h1 className="text-3xl font-bold text-gray-900 dark:text-white mb-4">OIDC 流程测试</h1>
|
||
<p className="text-lg text-gray-600 dark:text-gray-300">测试和验证 OpenID Connect 认证流程的各个环节</p>
|
||
</div>
|
||
|
||
{/* OIDC 流程步骤 */}
|
||
<Card>
|
||
<CardHeader>
|
||
<CardTitle className="flex items-center gap-2">
|
||
<Shield className="h-5 w-5" />
|
||
标准 OIDC 认证流程
|
||
</CardTitle>
|
||
<CardDescription>按照正确的 OIDC 架构,所有登录都在 OIDC Provider 中处理</CardDescription>
|
||
</CardHeader>
|
||
<CardContent className="space-y-6">
|
||
{/* 流程步骤图示 */}
|
||
<div className="grid grid-cols-1 md:grid-cols-5 gap-4 text-center">
|
||
<div className="flex flex-col items-center space-y-2">
|
||
<div className="w-12 h-12 bg-blue-100 dark:bg-blue-900 rounded-full flex items-center justify-center">
|
||
<User className="h-6 w-6 text-blue-600 dark:text-blue-300" />
|
||
</div>
|
||
<p className="text-sm font-medium">用户点击登录</p>
|
||
</div>
|
||
|
||
<div className="flex items-center justify-center">
|
||
<ArrowRight className="h-5 w-5 text-gray-400" />
|
||
</div>
|
||
|
||
<div className="flex flex-col items-center space-y-2">
|
||
<div className="w-12 h-12 bg-green-100 dark:bg-green-900 rounded-full flex items-center justify-center">
|
||
<Shield className="h-6 w-6 text-green-600 dark:text-green-300" />
|
||
</div>
|
||
<p className="text-sm font-medium">重定向到 Provider</p>
|
||
</div>
|
||
|
||
<div className="flex items-center justify-center">
|
||
<ArrowRight className="h-5 w-5 text-gray-400" />
|
||
</div>
|
||
|
||
<div className="flex flex-col items-center space-y-2">
|
||
<div className="w-12 h-12 bg-purple-100 dark:bg-purple-900 rounded-full flex items-center justify-center">
|
||
<Key className="h-6 w-6 text-purple-600 dark:text-purple-300" />
|
||
</div>
|
||
<p className="text-sm font-medium">返回授权码</p>
|
||
</div>
|
||
</div>
|
||
|
||
{/* 测试按钮 */}
|
||
<div className="flex justify-center">
|
||
<Button onClick={startOidcFlow} size="lg" className="px-8">
|
||
开始 OIDC 认证流程
|
||
</Button>
|
||
</div>
|
||
|
||
{/* 提示信息 */}
|
||
<Alert>
|
||
<Shield className="h-4 w-4" />
|
||
<AlertDescription>
|
||
点击上方按钮将重定向到 OIDC Provider 的登录页面。
|
||
<br />
|
||
<strong>演示账号:</strong> demouser / demo123
|
||
</AlertDescription>
|
||
</Alert>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
{/* Discovery 端点测试 */}
|
||
<Card>
|
||
<CardHeader>
|
||
<CardTitle className="flex items-center gap-2">
|
||
{getStatusIcon(testResults.discovery)}
|
||
Discovery 端点测试
|
||
</CardTitle>
|
||
<CardDescription>测试 OIDC Provider 的配置发现端点</CardDescription>
|
||
</CardHeader>
|
||
<CardContent className="space-y-4">
|
||
<Button onClick={testDiscoveryEndpoint} disabled={testResults.discovery === 'loading'}>
|
||
{testResults.discovery === 'loading' ? '测试中...' : '测试 Discovery 端点'}
|
||
</Button>
|
||
|
||
{testResults.discovery === 'error' && (
|
||
<Alert variant="destructive">
|
||
<XCircle className="h-4 w-4" />
|
||
<AlertDescription>
|
||
<strong>错误:</strong> {testResults.discoveryError}
|
||
</AlertDescription>
|
||
</Alert>
|
||
)}
|
||
|
||
{testResults.discovery === 'success' && testResults.discoveryData && (
|
||
<div className="space-y-4">
|
||
<Alert>
|
||
<CheckCircle className="h-4 w-4" />
|
||
<AlertDescription>
|
||
<strong>成功!</strong> OIDC Provider 配置已获取
|
||
</AlertDescription>
|
||
</Alert>
|
||
|
||
<div className="bg-gray-50 dark:bg-gray-800 p-4 rounded-lg">
|
||
<h4 className="font-semibold mb-3">Provider 信息</h4>
|
||
<div className="space-y-2 text-sm">
|
||
<p>
|
||
<strong>Issuer:</strong>{' '}
|
||
<code className="bg-gray-200 dark:bg-gray-700 px-1 rounded">
|
||
{testResults.discoveryData.issuer}
|
||
</code>
|
||
</p>
|
||
<p>
|
||
<strong>授权端点:</strong>{' '}
|
||
<code className="bg-gray-200 dark:bg-gray-700 px-1 rounded">
|
||
{testResults.discoveryData.authorization_endpoint}
|
||
</code>
|
||
</p>
|
||
<p>
|
||
<strong>令牌端点:</strong>{' '}
|
||
<code className="bg-gray-200 dark:bg-gray-700 px-1 rounded">
|
||
{testResults.discoveryData.token_endpoint}
|
||
</code>
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div>
|
||
<h4 className="font-semibold mb-3">支持的功能</h4>
|
||
<div className="flex flex-wrap gap-2">
|
||
{testResults.discoveryData.response_types_supported?.map((type: string) => (
|
||
<Badge key={type} variant="outline">
|
||
{type}
|
||
</Badge>
|
||
))}
|
||
</div>
|
||
<div className="flex flex-wrap gap-2 mt-2">
|
||
{testResults.discoveryData.scopes_supported?.map((scope: string) => (
|
||
<Badge key={scope} variant="secondary">
|
||
{scope}
|
||
</Badge>
|
||
))}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)}
|
||
</CardContent>
|
||
</Card>
|
||
|
||
{/* 架构说明 */}
|
||
<Card>
|
||
<CardHeader>
|
||
<CardTitle>正确的 OIDC 架构</CardTitle>
|
||
</CardHeader>
|
||
<CardContent className="space-y-4">
|
||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||
<div>
|
||
<h4 className="font-semibold text-green-600 mb-2">✅ 已实现</h4>
|
||
<ul className="space-y-1 text-sm">
|
||
<li>• OIDC Provider 包含登录页面</li>
|
||
<li>• 标准授权码流程</li>
|
||
<li>• PKCE 支持</li>
|
||
<li>• 内置会话管理</li>
|
||
<li>• 自动令牌刷新</li>
|
||
</ul>
|
||
</div>
|
||
<div>
|
||
<h4 className="font-semibold text-red-600 mb-2">❌ 已移除</h4>
|
||
<ul className="space-y-1 text-sm">
|
||
<li>• 客户端应用的登录页面</li>
|
||
<li>• 自定义认证逻辑</li>
|
||
<li>• 重复的用户管理</li>
|
||
<li>• 混合认证流程</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|