diff --git a/chat_web_backend/src/main/java/com/inspur/llm/chat/base/security/cas/CasProperties.java b/chat_web_backend/src/main/java/com/inspur/llm/chat/base/security/cas/CasProperties.java index 7100881..ebc3c0c 100644 --- a/chat_web_backend/src/main/java/com/inspur/llm/chat/base/security/cas/CasProperties.java +++ b/chat_web_backend/src/main/java/com/inspur/llm/chat/base/security/cas/CasProperties.java @@ -18,6 +18,8 @@ public class CasProperties { private String serverHost; /** CAS 服务器登录入口,通常 ${serverHost}/login */ private String serverLogin; + /** CAS 服务器登出入口,通常 ${serverHost}/logout */ + private String serverLogout; /** 本应用的 CAS 回调路径(servlet path),通常 /cas/login */ private String appLogin; } diff --git a/chat_web_backend/src/main/java/com/inspur/llm/chat/gpt/controller/app/LoginController.java b/chat_web_backend/src/main/java/com/inspur/llm/chat/gpt/controller/app/LoginController.java index 951cb84..4dfbee4 100644 --- a/chat_web_backend/src/main/java/com/inspur/llm/chat/gpt/controller/app/LoginController.java +++ b/chat_web_backend/src/main/java/com/inspur/llm/chat/gpt/controller/app/LoginController.java @@ -6,6 +6,8 @@ import com.inspur.llm.chat.base.constant.SysConfigConstants; import com.inspur.llm.chat.base.security.JwtTokenUtils; import com.inspur.llm.chat.base.security.Oauth2Token; import com.inspur.llm.chat.base.security.UserDetail; +import com.inspur.llm.chat.base.security.cas.CasProperties; +import com.inspur.llm.chat.base.security.cas.CasUrlBuilder; import com.inspur.llm.chat.base.util.RedisUtils; import com.inspur.llm.chat.base.validator.ValidatorUtil; import com.inspur.llm.chat.gpt.enums.ResponseEnum; @@ -27,6 +29,12 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import javax.servlet.http.HttpServletRequest; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; + import java.util.Optional; import java.util.Set; @@ -47,6 +55,8 @@ public class LoginController { private IUserService userService; @Autowired private RedisUtils redisUtils; + @Autowired + private CasProperties casProperties; /** * 登录 @@ -101,6 +111,46 @@ public class LoginController { return ResponseInfo.success(token); } + /** + * 退出登录:删 Redis 里的 token + 返回 CAS server logout URL(若用户用 CAS 登录的) + * + * 前端拿到 casLogoutUrl 后 window.location.href 跳过去,CAS 服务器会清掉 SSO cookie + * 并跳回 service 参数指定的页面(我们的 /login)。这样下次再点"统一身份登录"会重新跑 + * CAS 认证流,不会因为 SSO cookie 还在而直接静默登入。 + */ + @PostMapping("/api/logout") + public ResponseInfo> logout(HttpServletRequest request) { + Map data = new HashMap<>(); + try { + UserDetail userDetail = JwtTokenUtils.getLoginUser(); + if (userDetail != null) { + // 删 Redis 里这个 session 的 token + String key = RedisConstants.LOGIN_TOKEN_KEY + + userDetail.getId() + StringPoolConstant.COLON + userDetail.getSessionId(); + redisUtils.del(key); + } + } catch (Exception e) { + // Redis 不可用不影响登出流程 + e.printStackTrace(); + } + SecurityContextHolder.clearContext(); + + // 拼 CAS server logout URL + // service 参数:让 CAS 退出后回跳到我们的前端 login 页(动态用 X-Forwarded-Host) + String backToFront; + try { + backToFront = URLEncoder.encode( + CasUrlBuilder.buildServiceUrl(request, "/metalinfo/#/login"), + StandardCharsets.UTF_8.name()); + } catch (Exception e) { + backToFront = ""; + } + String casLogoutUrl = casProperties.getServerLogout() + + (backToFront.isEmpty() ? "" : "?service=" + backToFront); + data.put("casLogoutUrl", casLogoutUrl); + return ResponseInfo.success(data); + } + /** * 添加登录日志 * diff --git a/chat_web_backend/src/main/java/com/inspur/llm/chat/gpt/controller/gpt/FileController.java b/chat_web_backend/src/main/java/com/inspur/llm/chat/gpt/controller/gpt/FileController.java index bc17394..03ffda6 100644 --- a/chat_web_backend/src/main/java/com/inspur/llm/chat/gpt/controller/gpt/FileController.java +++ b/chat_web_backend/src/main/java/com/inspur/llm/chat/gpt/controller/gpt/FileController.java @@ -152,6 +152,8 @@ public class FileController extends BaseController { */ @GetMapping("/list") public ResponseInfo> listFile(@RequestParam Map map) { + // 强制按当前登录用户隔离,防止枚举别人的 knowledgeBaseId/folderId 拿到他人文件元信息 + map.put("userId", getSysUserId()); return fileService.listFile(new Query(map)); } diff --git a/chat_web_backend/src/main/resources/application.yml b/chat_web_backend/src/main/resources/application.yml index e1097a8..ae55e02 100644 --- a/chat_web_backend/src/main/resources/application.yml +++ b/chat_web_backend/src/main/resources/application.yml @@ -97,6 +97,7 @@ security: cas: server-host: http://192.168.203.20:8180 server-login: ${security.cas.server-host}/login + server-logout: ${security.cas.server-host}/logout app-login: /cas/login diff --git a/chat_web_backend/src/main/resources/mapper/gpt/UploadFileMapper.xml b/chat_web_backend/src/main/resources/mapper/gpt/UploadFileMapper.xml index 83b660f..66a0f8c 100644 --- a/chat_web_backend/src/main/resources/mapper/gpt/UploadFileMapper.xml +++ b/chat_web_backend/src/main/resources/mapper/gpt/UploadFileMapper.xml @@ -44,6 +44,7 @@ and t.folder_id = #{q.folderId} and t.embedding_id = #{q.embeddingId} and t.knowledge_base_id = #{q.knowledgeBaseId} + and t.user_id = #{q.userId} and t.type = #{q.type} and date_format(t.create_time,'%Y-%m-%d') >= #{q.startDate} and date_format(t.create_time,'%Y-%m-%d') <= #{q.endDate} diff --git a/chat_web_front/src/api/index.ts b/chat_web_front/src/api/index.ts index 4a846aa..55de47b 100644 --- a/chat_web_front/src/api/index.ts +++ b/chat_web_front/src/api/index.ts @@ -16,6 +16,13 @@ export function fetchVerify(data: object) { }) } +// 退出登录:清后端 Redis 里的 token,返回 CAS server logout URL(前端跳过去清 SSO cookie) +export function fetchLogout() { + return post({ + url: '/app/api/logout', + }) +} + // 获取用户信息 export function fetchSession() { return get({ diff --git a/chat_web_front/src/components/Operates.vue b/chat_web_front/src/components/Operates.vue index 5239d8c..53c5bde 100644 --- a/chat_web_front/src/components/Operates.vue +++ b/chat_web_front/src/components/Operates.vue @@ -57,6 +57,7 @@ import { useAuthStore } from "@/store"; import { reactive, watch, computed } from "vue"; import { useRouter, useRoute } from "vue-router"; import { User, Setting, SwitchButton, TopRight } from "@element-plus/icons-vue"; +import { fetchLogout } from "@/api"; const authStore = useAuthStore(); const isAdmin = computed(() => authStore.session?.admind === true); @@ -112,9 +113,22 @@ const openExtLink = (url: string) => { window.open(url, '_blank'); }; -const quit = () => { +const quit = async () => { + // 调后端 logout:清 Redis token + 拿 CAS 服务器登出 URL + // 拿到后跳过去清 CAS SSO cookie,否则下次"统一身份登录"会因为 cookie 还在直接静默登入 + let casLogoutUrl: string | null = null; + try { + const res: any = await fetchLogout(); + casLogoutUrl = res?.data?.casLogoutUrl || null; + } catch (e) { + console.warn("logout API 调用失败,本地清理后跳登录页", e); + } authStore.removeToken(); - router.push("/login"); + if (casLogoutUrl) { + window.location.href = casLogoutUrl; + } else { + router.push("/login"); + } }; const goProfile = () => {