|
@@ -30,10 +30,16 @@ import io.swagger.v3.oas.annotations.Parameter;
|
|
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
|
|
import jakarta.annotation.Resource;
|
|
import jakarta.annotation.Resource;
|
|
|
import jakarta.annotation.security.PermitAll;
|
|
import jakarta.annotation.security.PermitAll;
|
|
|
|
|
+import jakarta.servlet.http.Cookie;
|
|
|
import jakarta.servlet.http.HttpServletRequest;
|
|
import jakarta.servlet.http.HttpServletRequest;
|
|
|
import jakarta.validation.Valid;
|
|
import jakarta.validation.Valid;
|
|
|
|
|
+import org.springframework.http.HttpHeaders;
|
|
|
|
|
+import org.springframework.http.ResponseCookie;
|
|
|
import org.springframework.web.bind.annotation.*;
|
|
import org.springframework.web.bind.annotation.*;
|
|
|
|
|
|
|
|
|
|
+import jakarta.servlet.http.HttpServletResponse;
|
|
|
|
|
+import java.time.Duration;
|
|
|
|
|
+import java.time.LocalDateTime;
|
|
|
import java.util.HashSet;
|
|
import java.util.HashSet;
|
|
|
import java.util.List;
|
|
import java.util.List;
|
|
|
import java.util.Set;
|
|
import java.util.Set;
|
|
@@ -83,16 +89,20 @@ public class WebLoginController {
|
|
|
@PermitAll
|
|
@PermitAll
|
|
|
@TenantIgnore
|
|
@TenantIgnore
|
|
|
@Operation(summary = "使用账号密码登录")
|
|
@Operation(summary = "使用账号密码登录")
|
|
|
- public CommonResult<WebLoginVO> login(@RequestBody @Valid AuthLoginReqVO reqVO) {
|
|
|
|
|
- return success(buildWebLoginVO(authService.login(reqVO)));
|
|
|
|
|
|
|
+ public CommonResult<WebLoginVO> login(@RequestBody @Valid AuthLoginReqVO reqVO, HttpServletRequest request, HttpServletResponse response) {
|
|
|
|
|
+ WebLoginVO loginVO = buildWebLoginVO(authService.login(reqVO));
|
|
|
|
|
+ setAuthCookie(request, response, loginVO.getAccessToken(), loginVO.getExpiresTime());
|
|
|
|
|
+ return success(loginVO);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
@PostMapping("/sms-login")
|
|
@PostMapping("/sms-login")
|
|
|
@PermitAll
|
|
@PermitAll
|
|
|
@TenantIgnore
|
|
@TenantIgnore
|
|
|
@Operation(summary = "使用短信验证码登录")
|
|
@Operation(summary = "使用短信验证码登录")
|
|
|
- public CommonResult<WebLoginVO> smsLogin(@RequestBody @Valid AuthSmsLoginReqVO reqVO) {
|
|
|
|
|
- return success(buildWebLoginVO(authService.smsLogin(reqVO)));
|
|
|
|
|
|
|
+ public CommonResult<WebLoginVO> smsLogin(@RequestBody @Valid AuthSmsLoginReqVO reqVO, HttpServletRequest request, HttpServletResponse response) {
|
|
|
|
|
+ WebLoginVO loginVO = buildWebLoginVO(authService.smsLogin(reqVO));
|
|
|
|
|
+ setAuthCookie(request, response, loginVO.getAccessToken(), loginVO.getExpiresTime());
|
|
|
|
|
+ return success(loginVO);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
@PostMapping("/send-sms-code")
|
|
@PostMapping("/send-sms-code")
|
|
@@ -107,7 +117,11 @@ public class WebLoginController {
|
|
|
@PostMapping("/logout")
|
|
@PostMapping("/logout")
|
|
|
@PermitAll
|
|
@PermitAll
|
|
|
@Operation(summary = "登出系统")
|
|
@Operation(summary = "登出系统")
|
|
|
- public CommonResult<Boolean> logout(HttpServletRequest request) {
|
|
|
|
|
|
|
+ public CommonResult<Boolean> logout(HttpServletRequest request, HttpServletResponse response) {
|
|
|
|
|
+ // 复用登录时的Cookie设置方法,确保domain等参数完全一致
|
|
|
|
|
+ clearAuthCookie(request, response);
|
|
|
|
|
+
|
|
|
|
|
+ // 执行token作废业务逻辑
|
|
|
String token = SecurityFrameworkUtils.obtainAuthorization(request,
|
|
String token = SecurityFrameworkUtils.obtainAuthorization(request,
|
|
|
securityProperties.getTokenHeader(), securityProperties.getTokenParameter());
|
|
securityProperties.getTokenHeader(), securityProperties.getTokenParameter());
|
|
|
if (StrUtil.isNotBlank(token)) {
|
|
if (StrUtil.isNotBlank(token)) {
|
|
@@ -144,4 +158,87 @@ public class WebLoginController {
|
|
|
|
|
|
|
|
return webLoginVO;
|
|
return webLoginVO;
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 设置认证Cookie
|
|
|
|
|
+ */
|
|
|
|
|
+ private void setAuthCookie(HttpServletRequest request, HttpServletResponse response, String token, LocalDateTime expiresTime) {
|
|
|
|
|
+ long maxAgeSeconds = 0;
|
|
|
|
|
+ if (expiresTime != null) {
|
|
|
|
|
+ Duration duration = Duration.between(LocalDateTime.now(), expiresTime);
|
|
|
|
|
+ maxAgeSeconds = duration.getSeconds();
|
|
|
|
|
+ if (maxAgeSeconds < 0) {
|
|
|
|
|
+ maxAgeSeconds = 0;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ buildCookie(request, response, token, maxAgeSeconds);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 清除认证Cookie(登出时调用)
|
|
|
|
|
+ */
|
|
|
|
|
+ private void clearAuthCookie(HttpServletRequest request, HttpServletResponse response) {
|
|
|
|
|
+ buildCookie(request, response, "", 0);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 构建并设置Cookie(统一登录和登出的Cookie逻辑)
|
|
|
|
|
+ */
|
|
|
|
|
+ private void buildCookie(HttpServletRequest request, HttpServletResponse response, String token, long maxAgeSeconds) {
|
|
|
|
|
+ // 判断是否为HTTPS(考虑代理场景,检查X-Forwarded-Proto头)
|
|
|
|
|
+ boolean isHttps = isHttpsRequest(request);
|
|
|
|
|
+ String sameSiteVal = isHttps ? "None" : "Lax";
|
|
|
|
|
+
|
|
|
|
|
+ ResponseCookie.ResponseCookieBuilder cookieBuilder = ResponseCookie.from("access_token", token)
|
|
|
|
|
+ .path("/")
|
|
|
|
|
+ .maxAge(maxAgeSeconds)
|
|
|
|
|
+ .httpOnly(true)
|
|
|
|
|
+ .secure(isHttps)
|
|
|
|
|
+ .sameSite(sameSiteVal);
|
|
|
|
|
+
|
|
|
|
|
+ // 判断是否为本地环境:检查Origin、Host、RemoteAddr
|
|
|
|
|
+ if (!isLocalEnvironment(request)) {
|
|
|
|
|
+ cookieBuilder.domain(".learn-ai.com.cn");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ response.addHeader(HttpHeaders.SET_COOKIE, cookieBuilder.build().toString());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 判断请求是否为HTTPS(考虑代理场景)
|
|
|
|
|
+ */
|
|
|
|
|
+ private boolean isHttpsRequest(HttpServletRequest request) {
|
|
|
|
|
+ // 直接检查请求scheme
|
|
|
|
|
+ if ("https".equalsIgnoreCase(request.getScheme())) {
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+ // 检查代理头(Nginx等代理场景)
|
|
|
|
|
+ String forwardedProto = request.getHeader("X-Forwarded-Proto");
|
|
|
|
|
+ if ("https".equalsIgnoreCase(forwardedProto)) {
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+ // 检查X-Forwarded-Ssl头
|
|
|
|
|
+ String forwardedSsl = request.getHeader("X-Forwarded-Ssl");
|
|
|
|
|
+ return "on".equalsIgnoreCase(forwardedSsl);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 判断是否为本地开发环境
|
|
|
|
|
+ * 不依赖单一的Origin头,增加Host和RemoteAddr的判断
|
|
|
|
|
+ */
|
|
|
|
|
+ private boolean isLocalEnvironment(HttpServletRequest request) {
|
|
|
|
|
+ // 检查Origin头
|
|
|
|
|
+ String origin = request.getHeader("Origin");
|
|
|
|
|
+ if (origin != null && origin.contains("localhost")) {
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+ // 检查Host头
|
|
|
|
|
+ String host = request.getHeader("Host");
|
|
|
|
|
+ if (host != null && (host.contains("localhost") || host.contains("127.0.0.1"))) {
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+ // 检查远程IP
|
|
|
|
|
+ String remoteAddr = request.getRemoteAddr();
|
|
|
|
|
+ return "127.0.0.1".equals(remoteAddr) || "localhost".equals(remoteAddr) || "0:0:0:0:0:0:0:1".equals(remoteAddr);
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|