添加管理员登陆,添加nginx反向代理配置,
This commit is contained in:
+230
-59
@@ -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,
|
||||
},
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user