fix: 完善单点登录传递回调地址和serverUrl的功能。优化token刷新机制,判断单点登录和管理员登录等等不同路径的处理机制。提示词管理的模板数据查找的时候只需要返回固定的5个类型。隐藏评查点设置中关于抽取的自定义模板的选择。

This commit is contained in:
2025-11-11 14:25:44 +08:00
parent 95381ddcc2
commit 12ec2ad7bd
11 changed files with 238 additions and 85 deletions
+55 -7
View File
@@ -1,7 +1,7 @@
import { type LoaderFunctionArgs, redirect } from "@remix-run/node";
import { createUserSession, saveUserInfo, sessionStorage } from "~/api/login/auth.server";
import { OAuthClient } from "~/api/login/oauth-client";
import { OAUTH_CONFIG } from "~/config/api-config";
import { getServerOAuthConfigRuntime } from "~/config/oauth-secret.server";
import { JWTUtils, type UserInfoForJWT } from "~/utils/jwt";
/**
@@ -79,16 +79,20 @@ export async function loader({ request }: LoaderFunctionArgs) {
console.log("✅ OAuth2.0回调参数验证通过");
// 声明在 try 外部,以便在 catch 中访问
let tokenResponse = null;
const oauthClient = new OAuthClient(getServerOAuthConfigRuntime());
try {
console.log("🔧 开始处理OAuth2.0回调");
const oauthClient = new OAuthClient(OAUTH_CONFIG)
// 开始获取访问令牌
const tokenResponse = await oauthClient.getAccessToken(code);
tokenResponse = await oauthClient.getAccessToken(code);
if (!tokenResponse) {
console.error("获取访问令牌失败");
return redirect("/login?error=token_error")
// 注意:此时还没有 access_token,无法调用 IDaaS 登出
// 只能重定向到登录页,让用户重新开始登录流程
return redirectToLoginWithError(request, "获取访问令牌失败,请重试");
}
console.log("✅ [Callback] 访问令牌获取成功");
@@ -96,7 +100,22 @@ export async function loader({ request }: LoaderFunctionArgs) {
const userInfo = await oauthClient.getUserInfo(tokenResponse.access_token);
if(!userInfo || !userInfo.success){
console.error('获取用户信息失败:',userInfo);
return redirect('/login?error=userinfo_error')
// 🔑 关键:此时 IDaaS 那边已经登录成功,但我们获取用户信息失败
// 需要调用 IDaaS 登出,清除 IDaaS 的登录状态,避免用户下次登录时出现问题
try {
const logoutUrl = `${url.protocol}//${url.host}/login`;
const logoutSuccess = await oauthClient.logout(tokenResponse.access_token, logoutUrl);
if (logoutSuccess) {
console.log("✅ [Callback] 已清除 IDaaS 登录状态");
} else {
console.warn("⚠️ [Callback] 清除 IDaaS 登录状态失败");
}
} catch (logoutError) {
console.error("❌ [Callback] 调用 IDaaS 登出时出错:", logoutError);
}
return redirectToLoginWithError(request, "获取用户信息失败,请重试");
}
console.log("✅ [Callback] 用户信息获取成功");
@@ -128,7 +147,20 @@ export async function loader({ request }: LoaderFunctionArgs) {
const saveResult = await saveUserInfo(userInfo.data, tempToken, area);
if (!saveResult.success) {
console.error("保存用户信息到数据库失败:", saveResult.error);
// 注意:即使保存到数据库失败,我们仍然继续登录流程,因为用户已经通过了身份验证
// 🔑 保存用户信息失败,需要清除 IDaaS 登录状态
try {
const logoutUrl = `${url.protocol}//${url.host}/login`;
const logoutSuccess = await oauthClient.logout(tokenResponse.access_token, logoutUrl);
if (logoutSuccess) {
console.log("✅ [Callback] 已清除 IDaaS 登录状态(数据库保存失败)");
} else {
console.warn("⚠️ [Callback] 清除 IDaaS 登录状态失败(数据库保存失败)");
}
} catch (logoutError) {
console.error("❌ [Callback] 调用 IDaaS 登出时出错(数据库保存失败):", logoutError);
}
return redirectToLoginWithError(request, "保存用户信息失败,请重新登录");
}
@@ -182,6 +214,22 @@ export async function loader({ request }: LoaderFunctionArgs) {
} catch (error) {
console.error("OAuth2.0回调处理失败:", error);
// 🔑 如果已经获取到了 access_token,需要清除 IDaaS 登录状态
if (tokenResponse?.access_token) {
try {
const logoutUrl = `${url.protocol}//${url.host}/login`;
const logoutSuccess = await oauthClient.logout(tokenResponse.access_token, logoutUrl);
if (logoutSuccess) {
console.log("✅ [Callback] 已清除 IDaaS 登录状态(回调处理异常)");
} else {
console.warn("⚠️ [Callback] 清除 IDaaS 登录状态失败(回调处理异常)");
}
} catch (logoutError) {
console.error("❌ [Callback] 调用 IDaaS 登出时出错(回调处理异常):", logoutError);
}
}
return redirectToLoginWithError(request, "登录回调处理失败,请重新登录");
}
}
+6 -6
View File
@@ -2,7 +2,7 @@ import { useEffect, useState } from "react";
import { useActionData, useLoaderData, Form } from "@remix-run/react";
import { type MetaFunction, type LoaderFunctionArgs, type ActionFunctionArgs, redirect } from "@remix-run/node";
import { OAuthClient } from "~/api/login/oauth-client";
import { OAUTH_CONFIG } from "~/config/api-config";
import { CLIENT_OAUTH_CONFIG } from "~/config/api-config";
import { getUserSession, getSession, simpleRootLogin } from "~/api/login/auth.server";
import styles from "~/styles/pages/login.css?url";
import { toastService } from "~/components/ui";
@@ -114,8 +114,8 @@ export default function Login() {
// 处理OAuth2.0登录
const handleOAuthLogin = () => {
try {
// 创建OAuth客户端
const oauthClient = new OAuthClient(OAUTH_CONFIG);
// 创建OAuth客户端(使用客户端安全配置,不包含 clientSecret
const oauthClient = new OAuthClient(CLIENT_OAUTH_CONFIG);
// 生成状态值
const state = oauthClient.generateState();
@@ -172,9 +172,9 @@ export default function Login() {
};
useEffect(() => {
// 检查OAuth配置是否完整
if (!OAUTH_CONFIG.serverUrl || !OAUTH_CONFIG.clientId || !OAUTH_CONFIG.clientSecret) {
console.error("OAuth2.0配置不完整:", OAUTH_CONFIG);
// 检查OAuth配置是否完整(客户端不需要检查 clientSecret
if (!CLIENT_OAUTH_CONFIG.serverUrl || !CLIENT_OAUTH_CONFIG.clientId) {
console.error("OAuth2.0配置不完整:", CLIENT_OAUTH_CONFIG);
}
}, []);
+18 -12
View File
@@ -1,37 +1,43 @@
import { type LoaderFunctionArgs, redirect } from "@remix-run/node";
import { OAuthClient } from "~/api/login/oauth-client";
import { OAUTH_CONFIG } from "~/config/api-config";
import { getServerOAuthConfigRuntime } from "~/config/oauth-secret.server";
import { sessionStorage } from "~/api/login/auth.server";
export async function loader({ request }: LoaderFunctionArgs) {
const session = await sessionStorage.getSession(request.headers.get("Cookie"));
// 获取访问令牌
// 获取访问令牌和用户角色
const accessToken = session.get("accessToken");
if (accessToken) {
const userRole = session.get("userRole");
// 🔑 只有非 admin 用户才需要调用 IDaaS 单点登出
const isAdmin = userRole === 'admin';
if (accessToken && !isAdmin) {
try {
// 创建OAuth客户端
const oauthClient = new OAuthClient(OAUTH_CONFIG);
// 🔒 安全:使用服务器端专用函数获取完整配置
const oauthClient = new OAuthClient(getServerOAuthConfigRuntime());
// 构建登出后重定向URL
const url = new URL(request.url);
const redirectUrl = url.searchParams.get("redirect") || `${url.protocol}//${url.host}/login`;
// 调用IDaaS单点登出
const logoutSuccess = await oauthClient.logout(accessToken, redirectUrl);
if (!logoutSuccess) {
console.warn("IDaaS单点登出失败,但仍清除本地会话");
}
} catch (error) {
console.error("单点登出过程中出错:", error);
}
} else if (isAdmin) {
console.log("admin 用户登出,跳过 IDaaS 单点登出");
}
// 无论IDaaS登出是否成功,都清除本地会话
const cookie = await sessionStorage.destroySession(session);
return redirect("/login", {
headers: {
"Set-Cookie": cookie
+1 -1
View File
@@ -52,7 +52,7 @@ export async function loader({ request }: LoaderFunctionArgs) {
const page = parseInt(url.searchParams.get('page') || '1', 10);
const pageSize = parseInt(url.searchParams.get('pageSize') || '10', 10);
// console.log('加载提示词模板参数:', { name, type, status, page, pageSize });
// console.log('加载提示词模板参数:', { name, type: typeParam, status, page, pageSize });
// 从 API 获取数据
const result = await getPromptTemplates({
+1 -1
View File
@@ -372,7 +372,7 @@ export default function RuleNew() {
// 添加自定义选项
const optionsWithCustom = [
...response.data,
{ value: 'custom', label: '自定义' }
// { value: 'custom', label: '自定义' }
];
setVlmFieldTypeOptions(optionsWithCustom);
} else if (response.error) {