fix: 修改单点登录保存用户的jwt的生成,通过user_id为login,绕过验证进行表的增改。
This commit is contained in:
@@ -48,6 +48,7 @@ export interface UserInfo {
|
|||||||
status?: number; // 账户状态: 0=正常, 1=禁用
|
status?: number; // 账户状态: 0=正常, 1=禁用
|
||||||
is_leader?: boolean; // 是否为部门负责人
|
is_leader?: boolean; // 是否为部门负责人
|
||||||
area?: string; // 用户所属地区
|
area?: string; // 用户所属地区
|
||||||
|
id?: string | number; // 临时的用户id
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -538,16 +539,24 @@ async function callIDaaSLogout(accessToken: string, appId: string): Promise<void
|
|||||||
* 保存用户信息到数据库
|
* 保存用户信息到数据库
|
||||||
*
|
*
|
||||||
* 此函数实现以下逻辑:
|
* 此函数实现以下逻辑:
|
||||||
* 1. 根据 userInfo.sub 查询 sso_users 表中是否已存在该用户
|
* 1. 内部生成临时 JWT(user_id 为 'login',仅用于数据库操作)
|
||||||
* 2. 如果存在,则更新用户信息(如果用户已有 area 值则不更新)
|
* 2. 根据 userInfo.sub 查询 sso_users 表中是否已存在该用户
|
||||||
* 3. 如果不存在,则插入新的用户记录
|
* 3. 如果存在,则更新用户信息(如果用户已有 area 值则不更新)
|
||||||
|
* 4. 如果不存在,则插入新的用户记录
|
||||||
|
* 5. 返回保存的用户数据和临时 JWT
|
||||||
*
|
*
|
||||||
* @param userInfo - 从 IDaaS 获取的用户信息
|
* @param userInfo - 从 IDaaS 获取的用户信息
|
||||||
* @param token - JWT令牌
|
* @param userRole - 用户角色
|
||||||
|
* @param tokenExpiresIn - Token过期时间(秒)
|
||||||
* @param area - 用户所属地区,根据端口号确定
|
* @param area - 用户所属地区,根据端口号确定
|
||||||
* @returns Promise<{success: boolean, data?: SsoUser, error?: string}>
|
* @returns Promise<{success: boolean, data?: SsoUser, tempToken?: string, error?: string}>
|
||||||
*/
|
*/
|
||||||
export async function saveUserInfo(userInfo: UserInfo, token?: string, area?: string): Promise<{success: boolean, data?: SsoUser, error?: string}> {
|
export async function saveUserInfo(
|
||||||
|
userInfo: UserInfo,
|
||||||
|
userRole: UserRole,
|
||||||
|
tokenExpiresIn: number,
|
||||||
|
area?: string
|
||||||
|
): Promise<{success: boolean, data?: SsoUser, tempToken?: string, error?: string}> {
|
||||||
try {
|
try {
|
||||||
console.log("开始保存用户信息", userInfo);
|
console.log("开始保存用户信息", userInfo);
|
||||||
|
|
||||||
@@ -556,13 +565,30 @@ export async function saveUserInfo(userInfo: UserInfo, token?: string, area?: st
|
|||||||
return { success: false, error: "用户唯一标识 sub 不能为空" };
|
return { success: false, error: "用户唯一标识 sub 不能为空" };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 🔒 安全:在服务端生成临时 JWT,user_id 使用占位符 'login'
|
||||||
|
// 这样客户端无法看到真实的 user_id
|
||||||
|
const tempUserInfo: UserInfoForJWT = {
|
||||||
|
sub: userInfo.sub,
|
||||||
|
user_id: 'login', // 使用占位符,避免在客户端暴露真实ID
|
||||||
|
username: 'login',
|
||||||
|
nick_name: userInfo.nick_name || userInfo.nickname || userInfo.name || "未知用户",
|
||||||
|
email: userInfo.email,
|
||||||
|
phone_number: userInfo.phone_number,
|
||||||
|
ou_id: userInfo.ou_id || "default",
|
||||||
|
ou_name: userInfo.ou_name || "未知部门",
|
||||||
|
is_leader: userInfo.is_leader || false,
|
||||||
|
user_role: userRole
|
||||||
|
};
|
||||||
|
|
||||||
|
const tempToken = JWTUtils.generateJWT(tempUserInfo, tokenExpiresIn);
|
||||||
|
|
||||||
// 1. 根据 sub 查询是否已存在该用户
|
// 1. 根据 sub 查询是否已存在该用户
|
||||||
const existingUserResult = await postgrestGet<SsoUser[]>("sso_users", {
|
const existingUserResult = await postgrestGet<SsoUser[]>("sso_users", {
|
||||||
filter: {
|
filter: {
|
||||||
"sub": `eq.${userInfo.sub}`,
|
"sub": `eq.${userInfo.sub}`,
|
||||||
"deleted_at": "is.null" // 只查询未删除的记录
|
"deleted_at": "is.null" // 只查询未删除的记录
|
||||||
},
|
},
|
||||||
token
|
token: tempToken
|
||||||
});
|
});
|
||||||
|
|
||||||
if (existingUserResult.error) {
|
if (existingUserResult.error) {
|
||||||
@@ -603,7 +629,7 @@ export async function saveUserInfo(userInfo: UserInfo, token?: string, area?: st
|
|||||||
"sso_users",
|
"sso_users",
|
||||||
userData,
|
userData,
|
||||||
{ id: existingUser.id! },
|
{ id: existingUser.id! },
|
||||||
token
|
tempToken
|
||||||
);
|
);
|
||||||
|
|
||||||
if (updateResult.error) {
|
if (updateResult.error) {
|
||||||
@@ -614,7 +640,8 @@ export async function saveUserInfo(userInfo: UserInfo, token?: string, area?: st
|
|||||||
console.log("用户信息更新成功");
|
console.log("用户信息更新成功");
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
data: Array.isArray(updateResult.data) ? updateResult.data[0] : updateResult.data as unknown as SsoUser
|
data: Array.isArray(updateResult.data) ? updateResult.data[0] : updateResult.data as unknown as SsoUser,
|
||||||
|
tempToken // 返回临时 JWT
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
// 3. 用户不存在,执行插入操作,设置地区信息
|
// 3. 用户不存在,执行插入操作,设置地区信息
|
||||||
@@ -626,7 +653,7 @@ export async function saveUserInfo(userInfo: UserInfo, token?: string, area?: st
|
|||||||
console.log("新用户,设置地区为:", area);
|
console.log("新用户,设置地区为:", area);
|
||||||
}
|
}
|
||||||
|
|
||||||
const insertResult = await postgrestPost<SsoUser[], SsoUser>("sso_users", userData as SsoUser, token);
|
const insertResult = await postgrestPost<SsoUser[], SsoUser>("sso_users", userData as SsoUser, tempToken);
|
||||||
|
|
||||||
if (insertResult.error) {
|
if (insertResult.error) {
|
||||||
console.error("插入用户失败:", insertResult.error);
|
console.error("插入用户失败:", insertResult.error);
|
||||||
@@ -638,12 +665,13 @@ export async function saveUserInfo(userInfo: UserInfo, token?: string, area?: st
|
|||||||
// 4. 给这个用户默认添加一个角色,角色为common
|
// 4. 给这个用户默认添加一个角色,角色为common
|
||||||
const userData_with_id = Array.isArray(insertResult.data) ? insertResult.data[0] : insertResult.data as unknown as SsoUser;
|
const userData_with_id = Array.isArray(insertResult.data) ? insertResult.data[0] : insertResult.data as unknown as SsoUser;
|
||||||
if (userData_with_id?.id) {
|
if (userData_with_id?.id) {
|
||||||
await addDefaultRole(userData_with_id.id, 2, token);
|
await addDefaultRole(userData_with_id.id, 2, tempToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
data: userData_with_id
|
data: userData_with_id,
|
||||||
|
tempToken // 返回临时 JWT
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -33,9 +33,9 @@ const portConfigs: Record<string, Partial<ApiConfig>> = {
|
|||||||
// 主要
|
// 主要
|
||||||
// 梅州
|
// 梅州
|
||||||
'51703': {
|
'51703': {
|
||||||
baseUrl: 'http://nas.7bm.co:8073',
|
baseUrl: 'http://10.79.97.17:8000',
|
||||||
documentUrl: 'http://nas.7bm.co:8073/docauditai/',
|
documentUrl: 'http://10.79.97.17:8000/docauditai/',
|
||||||
uploadUrl: 'http://nas.7bm.co:8073/admin/documents',
|
uploadUrl: 'http://10.79.97.17:8000/admin/documents',
|
||||||
oauth: {
|
oauth: {
|
||||||
redirectUri: 'http://10.79.97.17:51703/callback'
|
redirectUri: 'http://10.79.97.17:51703/callback'
|
||||||
}
|
}
|
||||||
@@ -99,9 +99,9 @@ const portConfigs: Record<string, Partial<ApiConfig>> = {
|
|||||||
const configs: Record<string, ApiConfig> = {
|
const configs: Record<string, ApiConfig> = {
|
||||||
// 开发环境
|
// 开发环境
|
||||||
development: {
|
development: {
|
||||||
baseUrl: 'http://172.16.0.55:8000', // FastAPI后端(包含/dify代理)
|
baseUrl: 'http://nas.7bm.co:8073', // FastAPI后端(包含/dify代理)
|
||||||
documentUrl: 'http://172.16.0.55:8000/docauditai/',
|
documentUrl: 'http://nas.7bm.co:8073/docauditai/',
|
||||||
uploadUrl: 'http://172.16.0.55:8000/admin/documents',
|
uploadUrl: 'http://nas.7bm.co:8073/admin/documents',
|
||||||
oauth: {
|
oauth: {
|
||||||
serverUrl: 'http://10.79.112.85', // IDaaS服务器地址
|
serverUrl: 'http://10.79.112.85', // IDaaS服务器地址
|
||||||
clientId: 'none',
|
clientId: 'none',
|
||||||
|
|||||||
+7
-18
@@ -126,25 +126,14 @@ export async function loader({ request }: LoaderFunctionArgs) {
|
|||||||
// 获取重定向URL
|
// 获取重定向URL
|
||||||
const redirectTo = url.searchParams.get("redirect") || "/";
|
const redirectTo = url.searchParams.get("redirect") || "/";
|
||||||
|
|
||||||
// 先生成一个临时 JWT
|
// 🔒 安全:临时 JWT 现在在 saveUserInfo() 内部生成,避免在客户端代码中暴露 user_id 逻辑
|
||||||
const tempUserInfo = {
|
|
||||||
sub: userInfo.data.sub,
|
|
||||||
// user_id: userInfo.data.user_id || "",
|
|
||||||
user_id: "",
|
|
||||||
username: userInfo.data.username,
|
|
||||||
nick_name: userInfo.data.nickname,
|
|
||||||
email: userInfo.data.email,
|
|
||||||
phone_number: userInfo.data.phone_number,
|
|
||||||
ou_id: userInfo.data.ou_id,
|
|
||||||
ou_name: userInfo.data.ou_name,
|
|
||||||
// is_leader: userInfo.data.is_leader,
|
|
||||||
is_leader: false,
|
|
||||||
user_role: userRole as 'common' | 'developer'
|
|
||||||
};
|
|
||||||
const tempToken = JWTUtils.generateJWT(tempUserInfo, tokenResponse.expires_in);
|
|
||||||
|
|
||||||
// 成功获取用户信息之后通过auth.server.ts中的saveUserInfo方法去写入自己的数据库中,通过sub作为唯一值去添加数据
|
// 成功获取用户信息之后通过auth.server.ts中的saveUserInfo方法去写入自己的数据库中,通过sub作为唯一值去添加数据
|
||||||
const saveResult = await saveUserInfo(userInfo.data, tempToken, area);
|
const saveResult = await saveUserInfo(
|
||||||
|
userInfo.data,
|
||||||
|
userRole,
|
||||||
|
tokenResponse.expires_in,
|
||||||
|
area
|
||||||
|
);
|
||||||
if (!saveResult.success) {
|
if (!saveResult.success) {
|
||||||
console.error("保存用户信息到数据库失败:", saveResult.error);
|
console.error("保存用户信息到数据库失败:", saveResult.error);
|
||||||
|
|
||||||
|
|||||||
@@ -29,8 +29,6 @@ module.exports = {
|
|||||||
NEXT_PUBLIC_PORT: '51703',
|
NEXT_PUBLIC_PORT: '51703',
|
||||||
NEXT_PUBLIC_CLIENT_ID: 'meizhou',
|
NEXT_PUBLIC_CLIENT_ID: 'meizhou',
|
||||||
NEXT_PUBLIC_API_PORT_CONFIG: '51703',
|
NEXT_PUBLIC_API_PORT_CONFIG: '51703',
|
||||||
// JWT认证配置
|
|
||||||
JWT_SECRET: 'docreview-jwt-secret-key-production-meizhou-2024',
|
|
||||||
OAUTH_CLIENT_SECRET: 'VYk1AC5XIJEfnEXwyq0u9JEY3fi3byCfSD58zANGeb'
|
OAUTH_CLIENT_SECRET: 'VYk1AC5XIJEfnEXwyq0u9JEY3fi3byCfSD58zANGeb'
|
||||||
},
|
},
|
||||||
error_file: './logs/meizhou-err.log',
|
error_file: './logs/meizhou-err.log',
|
||||||
@@ -64,8 +62,6 @@ module.exports = {
|
|||||||
NEXT_PUBLIC_PORT: '51704',
|
NEXT_PUBLIC_PORT: '51704',
|
||||||
NEXT_PUBLIC_CLIENT_ID: 'yunfu',
|
NEXT_PUBLIC_CLIENT_ID: 'yunfu',
|
||||||
NEXT_PUBLIC_API_PORT_CONFIG: '51704',
|
NEXT_PUBLIC_API_PORT_CONFIG: '51704',
|
||||||
// JWT认证配置
|
|
||||||
JWT_SECRET: 'docreview-jwt-secret-key-production-yunfu-2024',
|
|
||||||
OAUTH_CLIENT_SECRET: 'VYk1AC5XIJEfnEXwyq0u9JEY3fi3byCfSD58zANGeb'
|
OAUTH_CLIENT_SECRET: 'VYk1AC5XIJEfnEXwyq0u9JEY3fi3byCfSD58zANGeb'
|
||||||
},
|
},
|
||||||
error_file: './logs/yunfu-err.log',
|
error_file: './logs/yunfu-err.log',
|
||||||
@@ -98,8 +94,6 @@ module.exports = {
|
|||||||
NEXT_PUBLIC_PORT: '51705',
|
NEXT_PUBLIC_PORT: '51705',
|
||||||
NEXT_PUBLIC_CLIENT_ID: 'jieyang',
|
NEXT_PUBLIC_CLIENT_ID: 'jieyang',
|
||||||
NEXT_PUBLIC_API_PORT_CONFIG: '51705',
|
NEXT_PUBLIC_API_PORT_CONFIG: '51705',
|
||||||
// JWT认证配置
|
|
||||||
JWT_SECRET: 'docreview-jwt-secret-key-production-jieyang-2024',
|
|
||||||
OAUTH_CLIENT_SECRET: 'VYk1AC5XIJEfnEXwyq0u9JEY3fi3byCfSD58zANGeb'
|
OAUTH_CLIENT_SECRET: 'VYk1AC5XIJEfnEXwyq0u9JEY3fi3byCfSD58zANGeb'
|
||||||
},
|
},
|
||||||
error_file: './logs/jieyang-err.log',
|
error_file: './logs/jieyang-err.log',
|
||||||
@@ -132,8 +126,6 @@ module.exports = {
|
|||||||
NEXT_PUBLIC_PORT: '51706',
|
NEXT_PUBLIC_PORT: '51706',
|
||||||
NEXT_PUBLIC_CLIENT_ID: 'chaozhou',
|
NEXT_PUBLIC_CLIENT_ID: 'chaozhou',
|
||||||
NEXT_PUBLIC_API_PORT_CONFIG: '51706',
|
NEXT_PUBLIC_API_PORT_CONFIG: '51706',
|
||||||
// JWT认证配置
|
|
||||||
JWT_SECRET: 'docreview-jwt-secret-key-production-chaozhou-2024',
|
|
||||||
OAUTH_CLIENT_SECRET: 'VYk1AC5XIJEfnEXwyq0u9JEY3fi3byCfSD58zANGeb'
|
OAUTH_CLIENT_SECRET: 'VYk1AC5XIJEfnEXwyq0u9JEY3fi3byCfSD58zANGeb'
|
||||||
},
|
},
|
||||||
error_file: './logs/chaozhou-err.log',
|
error_file: './logs/chaozhou-err.log',
|
||||||
@@ -166,8 +158,6 @@ module.exports = {
|
|||||||
NEXT_PUBLIC_PORT: '51707',
|
NEXT_PUBLIC_PORT: '51707',
|
||||||
NEXT_PUBLIC_CLIENT_ID: 'province',
|
NEXT_PUBLIC_CLIENT_ID: 'province',
|
||||||
NEXT_PUBLIC_API_PORT_CONFIG: '51707',
|
NEXT_PUBLIC_API_PORT_CONFIG: '51707',
|
||||||
// JWT认证配置
|
|
||||||
JWT_SECRET: 'docreview-jwt-secret-key-production-province-2024',
|
|
||||||
OAUTH_CLIENT_SECRET: 'VYk1AC5XIJEfnEXwyq0u9JEY3fi3byCfSD58zANGeb'
|
OAUTH_CLIENT_SECRET: 'VYk1AC5XIJEfnEXwyq0u9JEY3fi3byCfSD58zANGeb'
|
||||||
},
|
},
|
||||||
error_file: './logs/province-err.log',
|
error_file: './logs/province-err.log',
|
||||||
|
|||||||
Reference in New Issue
Block a user