diff --git a/src/app/admin/page.tsx b/src/app/admin/page.tsx
index faeaf89..f9ddf55 100644
--- a/src/app/admin/page.tsx
+++ b/src/app/admin/page.tsx
@@ -303,6 +303,14 @@ interface SiteConfig {
TurnstileSiteKey?: string;
TurnstileSecretKey?: string;
DefaultUserTags?: string[];
+ EnableOIDCLogin?: boolean;
+ EnableOIDCRegistration?: boolean;
+ OIDCIssuer?: string;
+ OIDCAuthorizationEndpoint?: string;
+ OIDCTokenEndpoint?: string;
+ OIDCUserInfoEndpoint?: string;
+ OIDCClientId?: string;
+ OIDCClientSecret?: string;
}
// 视频源数据类型
@@ -4589,6 +4597,14 @@ const SiteConfigComponent = ({
TurnstileSiteKey: '',
TurnstileSecretKey: '',
DefaultUserTags: [],
+ EnableOIDCLogin: false,
+ EnableOIDCRegistration: false,
+ OIDCIssuer: '',
+ OIDCAuthorizationEndpoint: '',
+ OIDCTokenEndpoint: '',
+ OIDCUserInfoEndpoint: '',
+ OIDCClientId: '',
+ OIDCClientSecret: '',
});
// 豆瓣数据源相关状态
@@ -4662,6 +4678,14 @@ const SiteConfigComponent = ({
TurnstileSiteKey: config.SiteConfig.TurnstileSiteKey || '',
TurnstileSecretKey: config.SiteConfig.TurnstileSecretKey || '',
DefaultUserTags: config.SiteConfig.DefaultUserTags || [],
+ EnableOIDCLogin: config.SiteConfig.EnableOIDCLogin || false,
+ EnableOIDCRegistration: config.SiteConfig.EnableOIDCRegistration || false,
+ OIDCIssuer: config.SiteConfig.OIDCIssuer || '',
+ OIDCAuthorizationEndpoint: config.SiteConfig.OIDCAuthorizationEndpoint || '',
+ OIDCTokenEndpoint: config.SiteConfig.OIDCTokenEndpoint || '',
+ OIDCUserInfoEndpoint: config.SiteConfig.OIDCUserInfoEndpoint || '',
+ OIDCClientId: config.SiteConfig.OIDCClientId || '',
+ OIDCClientSecret: config.SiteConfig.OIDCClientSecret || '',
});
}
}, [config]);
@@ -5412,6 +5436,293 @@ const SiteConfigComponent = ({
+ {/* OIDC配置 */}
+
+
+ OIDC配置
+
+
+ {/* 启用OIDC登录 */}
+
+
+
+
+
+
+ 开启后登录页面将显示OIDC登录按钮
+
+
+
+ {/* 启用OIDC注册 */}
+
+
+
+
+
+
+ 开启后允许通过OIDC方式注册新用户(需要先启用OIDC登录)
+
+
+
+ {/* OIDC Issuer */}
+
+
+
+
+ setSiteSettings((prev) => ({
+ ...prev,
+ OIDCIssuer: e.target.value,
+ }))
+ }
+ className='flex-1 px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-green-500 focus:border-transparent'
+ />
+
+
+
+ OIDC提供商的Issuer URL,填写后可点击"自动发现"按钮自动获取端点配置
+
+
+
+ {/* Authorization Endpoint */}
+
+
+
+ setSiteSettings((prev) => ({
+ ...prev,
+ OIDCAuthorizationEndpoint: e.target.value,
+ }))
+ }
+ className='w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-green-500 focus:border-transparent'
+ />
+
+ 用户授权的端点URL
+
+
+
+ {/* Token Endpoint */}
+
+
+
+ setSiteSettings((prev) => ({
+ ...prev,
+ OIDCTokenEndpoint: e.target.value,
+ }))
+ }
+ className='w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-green-500 focus:border-transparent'
+ />
+
+ 交换授权码获取token的端点URL
+
+
+
+ {/* UserInfo Endpoint */}
+
+
+
+ setSiteSettings((prev) => ({
+ ...prev,
+ OIDCUserInfoEndpoint: e.target.value,
+ }))
+ }
+ className='w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-green-500 focus:border-transparent'
+ />
+
+ 获取用户信息的端点URL
+
+
+
+ {/* OIDC Client ID */}
+
+
+
+ setSiteSettings((prev) => ({
+ ...prev,
+ OIDCClientId: e.target.value,
+ }))
+ }
+ className='w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-green-500 focus:border-transparent'
+ />
+
+ 在OIDC提供商处注册应用后获得的Client ID
+
+
+
+ {/* OIDC Client Secret */}
+
+
+
+ setSiteSettings((prev) => ({
+ ...prev,
+ OIDCClientSecret: e.target.value,
+ }))
+ }
+ className='w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-green-500 focus:border-transparent'
+ />
+
+ 在OIDC提供商处注册应用后获得的Client Secret
+
+
+
+ {/* OIDC Redirect URI - 只读显示 */}
+
+
+
+
+
+
+
+ 这是系统自动生成的回调地址,基于环境变量SITE_BASE。请在OIDC提供商(如Keycloak、Auth0等)的应用配置中添加此地址作为允许的重定向URI
+
+
+
+
{/* 操作按钮 */}
)}
+
+ {/* OIDC登录按钮 */}
+ {siteConfig?.EnableOIDCLogin && shouldAskUsername && (
+
+
+
+
+ )}
{/* 版本信息显示 */}
diff --git a/src/app/oidc-register/page.tsx b/src/app/oidc-register/page.tsx
new file mode 100644
index 0000000..93215a8
--- /dev/null
+++ b/src/app/oidc-register/page.tsx
@@ -0,0 +1,170 @@
+'use client';
+
+import { useRouter } from 'next/navigation';
+import { Suspense, useEffect, useState } from 'react';
+
+import { CURRENT_VERSION } from '@/lib/version';
+
+import { useSite } from '@/components/SiteProvider';
+import { ThemeToggle } from '@/components/ThemeToggle';
+
+function OIDCRegisterPageClient() {
+ const router = useRouter();
+ const [username, setUsername] = useState('');
+ const [error, setError] = useState(null);
+ const [loading, setLoading] = useState(false);
+ const [oidcInfo, setOidcInfo] = useState(null);
+
+ const { siteName } = useSite();
+
+ // 检查OIDC session
+ useEffect(() => {
+ const checkSession = async () => {
+ try {
+ const res = await fetch('/api/auth/oidc/session-info');
+ if (res.ok) {
+ const data = await res.json();
+ setOidcInfo(data);
+ } else {
+ // session无效,跳转到登录页
+ router.replace('/login?error=' + encodeURIComponent('OIDC会话已过期'));
+ }
+ } catch (error) {
+ console.error('检查session失败:', error);
+ router.replace('/login');
+ }
+ };
+
+ checkSession();
+ }, [router]);
+
+ const handleSubmit = async (e: React.FormEvent) => {
+ e.preventDefault();
+ setError(null);
+
+ if (!username) {
+ setError('请输入用户名');
+ return;
+ }
+
+ try {
+ setLoading(true);
+ const res = await fetch('/api/auth/oidc/complete-register', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ username }),
+ });
+
+ if (res.ok) {
+ // 注册成功,跳转到首页
+ router.replace('/');
+ } else {
+ const data = await res.json().catch(() => ({}));
+ setError(data.error || '注册失败');
+ }
+ } catch (error) {
+ setError('网络错误,请稍后重试');
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ if (!oidcInfo) {
+ return (
+
+ );
+ }
+
+ return (
+
+
+
+
+
+
+ {siteName}
+
+
+ 完成OIDC注册
+
+
+ {/* OIDC信息显示 */}
+ {oidcInfo && (
+
+
+ {oidcInfo.email && (
+ <>
+ 邮箱: {oidcInfo.email}
+
+ >
+ )}
+ {oidcInfo.name && (
+ <>
+ 名称: {oidcInfo.name}
+ >
+ )}
+
+
+ )}
+
+
+
+
+ {/* 版本信息 */}
+
+ v{CURRENT_VERSION}
+
+
+ );
+}
+
+export default function OIDCRegisterPage() {
+ return (
+ Loading...}>
+
+
+ );
+}
diff --git a/src/lib/admin.types.ts b/src/lib/admin.types.ts
index 8d65d0d..6adc805 100644
--- a/src/lib/admin.types.ts
+++ b/src/lib/admin.types.ts
@@ -31,6 +31,15 @@ export interface AdminConfig {
TurnstileSiteKey?: string; // Cloudflare Turnstile Site Key
TurnstileSecretKey?: string; // Cloudflare Turnstile Secret Key
DefaultUserTags?: string[]; // 新注册用户的默认用户组
+ // OIDC配置
+ EnableOIDCLogin?: boolean; // 启用OIDC登录
+ EnableOIDCRegistration?: boolean; // 启用OIDC注册
+ OIDCIssuer?: string; // OIDC Issuer URL (用于自动发现)
+ OIDCAuthorizationEndpoint?: string; // 授权端点
+ OIDCTokenEndpoint?: string; // Token端点
+ OIDCUserInfoEndpoint?: string; // 用户信息端点
+ OIDCClientId?: string; // OIDC Client ID
+ OIDCClientSecret?: string; // OIDC Client Secret
};
UserConfig: {
Users: {
diff --git a/src/middleware.ts b/src/middleware.ts
index 408a695..780439e 100644
--- a/src/middleware.ts
+++ b/src/middleware.ts
@@ -133,6 +133,6 @@ function shouldSkipAuth(pathname: string): boolean {
// 配置middleware匹配规则
export const config = {
matcher: [
- '/((?!_next/static|_next/image|favicon.ico|login|register|warning|api/login|api/register|api/logout|api/cron|api/server-config|api/proxy-m3u8|api/cms-proxy|api/tvbox/subscribe|api/theme/css).*)',
+ '/((?!_next/static|_next/image|favicon.ico|login|register|oidc-register|warning|api/login|api/register|api/logout|api/auth/oidc|api/cron|api/server-config|api/proxy-m3u8|api/cms-proxy|api/tvbox/subscribe|api/theme/css).*)',
],
};