添加管理员登陆,添加nginx反向代理配置,

This commit is contained in:
2025-07-27 20:01:36 +08:00
parent 9a366d042a
commit 33363aba78
17 changed files with 2010 additions and 836 deletions
+230 -59
View File
@@ -21,6 +21,7 @@ import { createCookieSessionStorage } from "@remix-run/node";
import { tokenManager } from "./token-manager.server";
import { postgrestGet, postgrestPost, postgrestPut } from "../postgrest-client";
import { JWTUtils, type UserInfoForJWT } from "~/utils/jwt";
import { OAUTH_CONFIG, API_BASE_URL } from "~/config/api-config";
/**
* 用户角色类型定义
@@ -289,11 +290,11 @@ export async function getUserSession(request: Request) {
// 打印JWT重新生成信息
console.log("=== Token刷新时重新生成JWT ===");
console.log("原始userInfo:", userInfo);
console.log("重构的用户数据:", mockSavedUserData);
console.log("用户角色:", userRole);
console.log("新生成的JWT:", newJWT);
console.log("JWT过期时间:", JWTUtils.getJWTExpiration(newJWT));
// console.log("原始userInfo:", userInfo);
// console.log("重构的用户数据:", mockSavedUserData);
// console.log("用户角色:", userRole);
// console.log("新生成的JWT:", newJWT);
// console.log("JWT过期时间:", JWTUtils.getJWTExpiration(newJWT));
// 更新session中的JWT
if (!refreshedSession) {
@@ -329,13 +330,79 @@ export async function getUserSession(request: Request) {
}
/**
* 创建用户登录会话
* 创建用户登录会话(完整版)
*
* 在用户成功登录后调用此函数来创建完整的会话并设置 Cookie。
* 这个函数支持完整的OAuth2.0登录流程,包括token管理和用户信息存储。
*
* 处理流程:
* 1. 创建新的会话对象
* 2. 设置认证状态、用户角色、token信息
* 3. 保存用户信息和前端JWT
* 4. 生成签名的 Cookie
* 5. 返回重定向响应并设置 Cookie
*
* @param params - 会话创建参数
* @returns HTTP 302 重定向响应,包含设置 Cookie 的头部
*/
export async function createUserSession(params: {
isAuthenticated: boolean;
userRole: UserRole;
redirectTo: string;
accessToken?: string;
refreshToken?: string;
tokenExpiresIn?: number;
userInfo?: UserInfo;
frontendJWT?: string;
}) {
const session = await sessionStorage.getSession();
// 基础认证信息
session.set("isAuthenticated", params.isAuthenticated);
session.set("userRole", params.userRole);
// OAuth token信息
if (params.accessToken) {
session.set("accessToken", params.accessToken);
session.set("tokenIssuedAt", Date.now());
}
if (params.refreshToken) {
session.set("refreshToken", params.refreshToken);
}
if (params.tokenExpiresIn) {
session.set("tokenExpiresIn", params.tokenExpiresIn);
}
// 用户信息和JWT
if (params.userInfo) {
session.set("userInfo", params.userInfo);
}
if (params.frontendJWT) {
session.set("frontendJWT", params.frontendJWT);
}
const cookie = await sessionStorage.commitSession(session);
// console.log("创建完整会话 - 设置Cookie:", !!cookie);
// console.log("创建完整会话 - 用户角色:", params.userRole);
// console.log("创建完整会话 - 重定向到:", params.redirectTo);
return new Response(null, {
status: 302, // HTTP 重定向状态码
headers: {
Location: params.redirectTo, // 重定向目标 URL
"Set-Cookie": cookie, // 设置会话 Cookie
},
});
}
/**
* 创建用户登录会话(简化版)
*
* 在用户成功登录后调用此函数来创建会话并设置 Cookie。
* 这个函数通常在以下场景中使用:
* - OAuth2.0 登录成功后
* - 临时管理员登录
* - 其他认证方式成功后
* - 测试用户登录
* - 其他简单认证方式成功后
*
* 处理流程:
* 1. 创建新的会话对象
@@ -348,15 +415,15 @@ export async function getUserSession(request: Request) {
* @param redirectTo - 登录成功后重定向的 URL,默认为首页
* @returns HTTP 302 重定向响应,包含设置 Cookie 的头部
*/
export async function createUserSession(isAuthenticated: boolean, userRole: UserRole, redirectTo: string) {
export async function createSimpleUserSession(isAuthenticated: boolean, userRole: UserRole, redirectTo: string) {
const session = await sessionStorage.getSession();
session.set("isAuthenticated", isAuthenticated);
session.set("userRole", userRole);
const cookie = await sessionStorage.commitSession(session);
console.log("创建会话 - 设置Cookie:", !!cookie);
console.log("创建会话 - 用户角色:", userRole);
console.log("创建会话 - 重定向到:", redirectTo);
console.log("创建简化会话 - 设置Cookie:", !!cookie);
console.log("创建简化会话 - 用户角色:", userRole);
console.log("创建简化会话 - 重定向到:", redirectTo);
return new Response(null, {
status: 302, // HTTP 重定向状态码
@@ -373,14 +440,15 @@ export async function createUserSession(isAuthenticated: boolean, userRole: User
* 当用户主动登出或会话失效时调用此函数。
*
* 处理流程:
* 1. 获取当前用户的会话
* 2. 销毁会话数据(清除所有存储的信息)
* 3. 清除客户端的会话 Cookie
* 4. 重定向到登录页面
* 1. 获取当前用户的会话和访问令牌
* 2. 调用 IDaaS 单点登出接口
* 3. 销毁本地会话数据(清除所有存储的信息)
* 4. 清除客户端的会话 Cookie
* 5. 重定向到登录页面
*
* 注意事项:
* - 这个函数处理本地会话,不会调用 IDaaS 的单点登出
* - 如果需要全局登出,应该额外调用 IDaaS 的 SLO 接口
* - 这个函数会同时处理本地会话 IDaaS 的单点登出
* - 即使 IDaaS 登出失败,也会清除本地会话
* - 销毁会话后,用户需要重新登录才能访问受保护的页面
*
* @param request - Remix Request 对象,用于获取当前会话
@@ -388,6 +456,21 @@ export async function createUserSession(isAuthenticated: boolean, userRole: User
*/
export async function logout(request: Request) {
const session = await getSession(request);
// 获取访问令牌和应用ID,用于调用IDaaS单点登出
const accessToken = session.get("accessToken");
const appId = OAUTH_CONFIG.appId;
// 如果存在访问令牌,调用IDaaS单点登出
if (accessToken && appId) {
try {
await callIDaaSLogout(accessToken, appId);
console.log("IDaaS单点登出成功");
} catch (error) {
console.error("IDaaS单点登出失败:", error);
// 即使IDaaS登出失败,也继续清除本地会话
}
}
return new Response(null, {
status: 302, // HTTP 重定向状态码
@@ -398,6 +481,40 @@ export async function logout(request: Request) {
});
}
/**
* 调用IDaaS单点登出接口
*
* @param accessToken - 用户的访问令牌
* @param appId - 应用ID
* @returns Promise<void>
*/
async function callIDaaSLogout(accessToken: string, appId: string): Promise<void> {
const logoutUrl = `${OAUTH_CONFIG.serverUrl}/public/sp/slo/${appId}`;
const formData = new URLSearchParams();
formData.append('access_token', accessToken);
formData.append('redirect_url', encodeURIComponent(OAUTH_CONFIG.redirectUri));
try {
const response = await fetch(logoutUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: formData.toString(),
});
if (!response.ok) {
throw new Error(`IDaaS登出失败: ${response.status} ${response.statusText}`);
}
console.log("IDaaS单点登出请求成功");
} catch (error) {
console.error("调用IDaaS登出接口失败:", error);
throw error;
}
}
/**
* 保存用户信息到数据库
*
@@ -564,7 +681,7 @@ export async function addDefaultRole(userId: string, roleId: number = 2) {
*/
export async function getUserBySub(sub: string) {
try {
console.log(`查询用户: ${sub}`);
// console.log(`查询用户: ${sub}`);
const userResult = await postgrestGet<SsoUser[]>("sso_users", {
filter: {
@@ -595,49 +712,103 @@ export async function getUserBySub(sub: string) {
}
/**
* 创建用户登录会话(支持用户信息)
* 账号密码登录接口
*
* @param isAuthenticated - 是否已认证
* @param userRole - 用户角色
* @param redirectTo - 重定向URL
* @param userInfo - 可选的用户信息
* @returns HTTP重定向响应
* @param username - 用户名
* @param password - 密码
* @param redirectTo - 登录成功后重定向URL
* @returns HTTP重定向响应或错误响应
*/
export async function createUserSessionWithInfo(
isAuthenticated: boolean,
userRole: UserRole,
redirectTo: string,
userInfo?: Partial<SsoUser>
export async function simpleRootLogin(
username: string,
password: string,
redirectTo: string
) {
const session = await sessionStorage.getSession();
session.set("isAuthenticated", isAuthenticated);
session.set("userRole", userRole);
// 如果提供了用户信息,也保存到session中
if (userInfo) {
session.set("userInfo", {
sub: userInfo.sub,
user_id: userInfo.id,
username: userInfo.username,
nick_name: userInfo.nick_name,
email: userInfo.email,
ou_name: userInfo.ou_name,
is_leader: userInfo.is_leader,
user_role: userRole
try {
// 输入验证
if (!username?.trim() || !password?.trim()) {
return new Response(JSON.stringify({
success: false,
error: "用户名和密码不能为空"
}), {
status: 400,
headers: { "Content-Type": "application/json" }
});
}
// 调用登录接口
const loginResponse = await fetch(`${API_BASE_URL}/password_login`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
sub: username.trim(),
password: password.trim()
})
});
const loginResult = await loginResponse.json();
if (loginResult.code === 0 && loginResult.data) {
// 登录成功,构建用户信息
const userData = loginResult.data;
const userRole = 'common' as UserRole; // 默认角色
// 构建用户信息对象
const userInfo = {
sub: userData.sub,
user_id: userData.sub, // 使用sub作为user_id
username: userData.username,
nick_name: userData.nick_name,
phone_number: userData.phone_number,
email: userData.email,
ou_id: userData.ou_id,
ou_name: userData.ou_name,
is_leader: userData.is_leader,
user_role: userRole
};
// 创建会话
const session = await sessionStorage.getSession();
session.set("isAuthenticated", true);
session.set("userRole", userRole);
session.set("userInfo", userInfo);
const cookie = await sessionStorage.commitSession(session);
// console.log("账号密码登录成功 - 用户:", userData.username);
// console.log("账号密码登录成功 - 角色:", userRole);
// console.log("账号密码登录成功 - 重定向到:", redirectTo);
return new Response(null, {
status: 302,
headers: {
Location: redirectTo,
"Set-Cookie": cookie,
},
});
} else {
// 登录失败,返回错误信息
const errorMsg = loginResult.msg || "登录失败,请检查用户名和密码";
return new Response(JSON.stringify({
success: false,
error: errorMsg
}), {
status: 401,
headers: { "Content-Type": "application/json" }
});
}
} catch (error) {
console.error("登录请求失败:", error);
return new Response(JSON.stringify({
success: false,
error: "登录请求失败,请稍后重试"
}), {
status: 500,
headers: { "Content-Type": "application/json" }
});
}
const cookie = await sessionStorage.commitSession(session);
console.log("创建会话 - 设置Cookie:", !!cookie);
console.log("创建会话 - 用户角色:", userRole);
console.log("创建会话 - 用户信息:", userInfo?.username || "无");
console.log("创建会话 - 重定向到:", redirectTo);
return new Response(null, {
status: 302,
headers: {
Location: redirectTo,
"Set-Cookie": cookie,
},
});
}