From c20c168a13829b9c233d71076c9e440b73533f6c Mon Sep 17 00:00:00 2001 From: yorn <1057707203@qq.com> Date: Tue, 11 Nov 2025 21:09:11 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E6=94=B9=E5=8D=95=E7=82=B9?= =?UTF-8?q?=E7=99=BB=E5=BD=95=E4=BF=9D=E5=AD=98=E7=94=A8=E6=88=B7=E7=9A=84?= =?UTF-8?q?jwt=E7=9A=84=E7=94=9F=E6=88=90=EF=BC=8C=E9=80=9A=E8=BF=87user?= =?UTF-8?q?=5Fid=E4=B8=BAlogin=EF=BC=8C=E7=BB=95=E8=BF=87=E9=AA=8C?= =?UTF-8?q?=E8=AF=81=E8=BF=9B=E8=A1=8C=E8=A1=A8=E7=9A=84=E5=A2=9E=E6=94=B9?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/api/login/auth.server.ts | 52 +++++++++++++++++++++++++++--------- app/config/api-config.ts | 12 ++++----- app/routes/callback.tsx | 25 +++++------------ ecosystem.config.cjs | 10 ------- 4 files changed, 53 insertions(+), 46 deletions(-) diff --git a/app/api/login/auth.server.ts b/app/api/login/auth.server.ts index 45105c5..b8b07d6 100644 --- a/app/api/login/auth.server.ts +++ b/app/api/login/auth.server.ts @@ -48,6 +48,7 @@ export interface UserInfo { status?: number; // 账户状态: 0=正常, 1=禁用 is_leader?: boolean; // 是否为部门负责人 area?: string; // 用户所属地区 + id?: string | number; // 临时的用户id } /** @@ -538,16 +539,24 @@ async function callIDaaSLogout(accessToken: string, appId: string): Promise + * @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 { console.log("开始保存用户信息", userInfo); @@ -556,13 +565,30 @@ export async function saveUserInfo(userInfo: UserInfo, token?: string, area?: st 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 查询是否已存在该用户 const existingUserResult = await postgrestGet("sso_users", { filter: { "sub": `eq.${userInfo.sub}`, "deleted_at": "is.null" // 只查询未删除的记录 }, - token + token: tempToken }); if (existingUserResult.error) { @@ -603,7 +629,7 @@ export async function saveUserInfo(userInfo: UserInfo, token?: string, area?: st "sso_users", userData, { id: existingUser.id! }, - token + tempToken ); if (updateResult.error) { @@ -614,7 +640,8 @@ export async function saveUserInfo(userInfo: UserInfo, token?: string, area?: st console.log("用户信息更新成功"); return { 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 { // 3. 用户不存在,执行插入操作,设置地区信息 @@ -626,7 +653,7 @@ export async function saveUserInfo(userInfo: UserInfo, token?: string, area?: st console.log("新用户,设置地区为:", area); } - const insertResult = await postgrestPost("sso_users", userData as SsoUser, token); + const insertResult = await postgrestPost("sso_users", userData as SsoUser, tempToken); if (insertResult.error) { console.error("插入用户失败:", insertResult.error); @@ -638,12 +665,13 @@ export async function saveUserInfo(userInfo: UserInfo, token?: string, area?: st // 4. 给这个用户默认添加一个角色,角色为common const userData_with_id = Array.isArray(insertResult.data) ? insertResult.data[0] : insertResult.data as unknown as SsoUser; if (userData_with_id?.id) { - await addDefaultRole(userData_with_id.id, 2, token); + await addDefaultRole(userData_with_id.id, 2, tempToken); } return { success: true, - data: userData_with_id + data: userData_with_id, + tempToken // 返回临时 JWT }; } } catch (error) { diff --git a/app/config/api-config.ts b/app/config/api-config.ts index 687ff2a..06540be 100644 --- a/app/config/api-config.ts +++ b/app/config/api-config.ts @@ -33,9 +33,9 @@ const portConfigs: Record> = { // 主要 // 梅州 '51703': { - baseUrl: 'http://nas.7bm.co:8073', - documentUrl: 'http://nas.7bm.co:8073/docauditai/', - uploadUrl: 'http://nas.7bm.co:8073/admin/documents', + baseUrl: 'http://10.79.97.17:8000', + documentUrl: 'http://10.79.97.17:8000/docauditai/', + uploadUrl: 'http://10.79.97.17:8000/admin/documents', oauth: { redirectUri: 'http://10.79.97.17:51703/callback' } @@ -99,9 +99,9 @@ const portConfigs: Record> = { const configs: Record = { // 开发环境 development: { - baseUrl: 'http://172.16.0.55:8000', // FastAPI后端(包含/dify代理) - documentUrl: 'http://172.16.0.55:8000/docauditai/', - uploadUrl: 'http://172.16.0.55:8000/admin/documents', + baseUrl: 'http://nas.7bm.co:8073', // FastAPI后端(包含/dify代理) + documentUrl: 'http://nas.7bm.co:8073/docauditai/', + uploadUrl: 'http://nas.7bm.co:8073/admin/documents', oauth: { serverUrl: 'http://10.79.112.85', // IDaaS服务器地址 clientId: 'none', diff --git a/app/routes/callback.tsx b/app/routes/callback.tsx index 3c8a887..3c4b513 100644 --- a/app/routes/callback.tsx +++ b/app/routes/callback.tsx @@ -126,25 +126,14 @@ export async function loader({ request }: LoaderFunctionArgs) { // 获取重定向URL const redirectTo = url.searchParams.get("redirect") || "/"; - // 先生成一个临时 JWT - 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); - + // 🔒 安全:临时 JWT 现在在 saveUserInfo() 内部生成,避免在客户端代码中暴露 user_id 逻辑 // 成功获取用户信息之后通过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) { console.error("保存用户信息到数据库失败:", saveResult.error); diff --git a/ecosystem.config.cjs b/ecosystem.config.cjs index 5643b3e..a998d03 100644 --- a/ecosystem.config.cjs +++ b/ecosystem.config.cjs @@ -29,8 +29,6 @@ module.exports = { NEXT_PUBLIC_PORT: '51703', NEXT_PUBLIC_CLIENT_ID: 'meizhou', NEXT_PUBLIC_API_PORT_CONFIG: '51703', - // JWT认证配置 - JWT_SECRET: 'docreview-jwt-secret-key-production-meizhou-2024', OAUTH_CLIENT_SECRET: 'VYk1AC5XIJEfnEXwyq0u9JEY3fi3byCfSD58zANGeb' }, error_file: './logs/meizhou-err.log', @@ -64,8 +62,6 @@ module.exports = { NEXT_PUBLIC_PORT: '51704', NEXT_PUBLIC_CLIENT_ID: 'yunfu', NEXT_PUBLIC_API_PORT_CONFIG: '51704', - // JWT认证配置 - JWT_SECRET: 'docreview-jwt-secret-key-production-yunfu-2024', OAUTH_CLIENT_SECRET: 'VYk1AC5XIJEfnEXwyq0u9JEY3fi3byCfSD58zANGeb' }, error_file: './logs/yunfu-err.log', @@ -98,8 +94,6 @@ module.exports = { NEXT_PUBLIC_PORT: '51705', NEXT_PUBLIC_CLIENT_ID: 'jieyang', NEXT_PUBLIC_API_PORT_CONFIG: '51705', - // JWT认证配置 - JWT_SECRET: 'docreview-jwt-secret-key-production-jieyang-2024', OAUTH_CLIENT_SECRET: 'VYk1AC5XIJEfnEXwyq0u9JEY3fi3byCfSD58zANGeb' }, error_file: './logs/jieyang-err.log', @@ -132,8 +126,6 @@ module.exports = { NEXT_PUBLIC_PORT: '51706', NEXT_PUBLIC_CLIENT_ID: 'chaozhou', NEXT_PUBLIC_API_PORT_CONFIG: '51706', - // JWT认证配置 - JWT_SECRET: 'docreview-jwt-secret-key-production-chaozhou-2024', OAUTH_CLIENT_SECRET: 'VYk1AC5XIJEfnEXwyq0u9JEY3fi3byCfSD58zANGeb' }, error_file: './logs/chaozhou-err.log', @@ -166,8 +158,6 @@ module.exports = { NEXT_PUBLIC_PORT: '51707', NEXT_PUBLIC_CLIENT_ID: 'province', NEXT_PUBLIC_API_PORT_CONFIG: '51707', - // JWT认证配置 - JWT_SECRET: 'docreview-jwt-secret-key-production-province-2024', OAUTH_CLIENT_SECRET: 'VYk1AC5XIJEfnEXwyq0u9JEY3fi3byCfSD58zANGeb' }, error_file: './logs/province-err.log',