|
|
@ -6,6 +6,7 @@ import com.wok.supportbot.security.JwtTokenProvider; |
|
|
import com.wok.supportbot.security.LoginAttemptService; |
|
|
import com.wok.supportbot.security.LoginAttemptService; |
|
|
import com.wok.supportbot.security.PasswordValidator; |
|
|
import com.wok.supportbot.security.PasswordValidator; |
|
|
import com.wok.supportbot.service.SysUserService; |
|
|
import com.wok.supportbot.service.SysUserService; |
|
|
|
|
|
import jakarta.servlet.http.HttpServletRequest; |
|
|
import lombok.extern.slf4j.Slf4j; |
|
|
import lombok.extern.slf4j.Slf4j; |
|
|
import org.springframework.beans.factory.annotation.Value; |
|
|
import org.springframework.beans.factory.annotation.Value; |
|
|
import org.springframework.http.HttpHeaders; |
|
|
import org.springframework.http.HttpHeaders; |
|
|
@ -63,7 +64,7 @@ public class AuthController { |
|
|
* 登录成功后通过 httpOnly Cookie 返回 refresh token,body 中只返回 access token。 |
|
|
* 登录成功后通过 httpOnly Cookie 返回 refresh token,body 中只返回 access token。 |
|
|
*/ |
|
|
*/ |
|
|
@PostMapping("/login") |
|
|
@PostMapping("/login") |
|
|
public ResponseEntity<Map<String, Object>> login(@RequestBody Map<String, String> body) { |
|
|
|
|
|
|
|
|
public ResponseEntity<Map<String, Object>> login(@RequestBody Map<String, String> body, HttpServletRequest request) { |
|
|
String username = body.get("username"); |
|
|
String username = body.get("username"); |
|
|
String password = body.get("password"); |
|
|
String password = body.get("password"); |
|
|
if (username == null || password == null || username.isBlank() || password.isBlank()) { |
|
|
if (username == null || password == null || username.isBlank() || password.isBlank()) { |
|
|
@ -102,7 +103,7 @@ public class AuthController { |
|
|
loginAttemptService.recordSuccess(username); |
|
|
loginAttemptService.recordSuccess(username); |
|
|
|
|
|
|
|
|
Map<String, Object> userInfo = buildUserInfo(user, roleKeys); |
|
|
Map<String, Object> userInfo = buildUserInfo(user, roleKeys); |
|
|
ResponseCookie refreshCookie = buildRefreshCookie(refreshToken, 7 * 24 * 60 * 60L); |
|
|
|
|
|
|
|
|
ResponseCookie refreshCookie = buildRefreshCookie(refreshToken, 7 * 24 * 60 * 60L, request.isSecure()); |
|
|
|
|
|
|
|
|
log.info("用户登录成功: {}", username); |
|
|
log.info("用户登录成功: {}", username); |
|
|
return ResponseEntity.ok() |
|
|
return ResponseEntity.ok() |
|
|
@ -135,7 +136,8 @@ public class AuthController { |
|
|
*/ |
|
|
*/ |
|
|
@PostMapping("/refresh") |
|
|
@PostMapping("/refresh") |
|
|
public ResponseEntity<Map<String, Object>> refresh( |
|
|
public ResponseEntity<Map<String, Object>> refresh( |
|
|
@CookieValue(name = "refresh_token", required = false) String refreshToken) { |
|
|
|
|
|
|
|
|
@CookieValue(name = "refresh_token", required = false) String refreshToken, |
|
|
|
|
|
HttpServletRequest request) { |
|
|
if (refreshToken == null || !jwtTokenProvider.validateRefreshToken(refreshToken)) { |
|
|
if (refreshToken == null || !jwtTokenProvider.validateRefreshToken(refreshToken)) { |
|
|
return ResponseEntity.status(401).body(Map.of( |
|
|
return ResponseEntity.status(401).body(Map.of( |
|
|
"success", false, "message", "刷新令牌无效或已过期" |
|
|
"success", false, "message", "刷新令牌无效或已过期" |
|
|
@ -162,7 +164,7 @@ public class AuthController { |
|
|
String newAccessToken = jwtTokenProvider.generateToken(username, roleKeys, currentVer); |
|
|
String newAccessToken = jwtTokenProvider.generateToken(username, roleKeys, currentVer); |
|
|
String newRefreshToken = jwtTokenProvider.generateRefreshToken(username, currentVer); |
|
|
String newRefreshToken = jwtTokenProvider.generateRefreshToken(username, currentVer); |
|
|
|
|
|
|
|
|
ResponseCookie refreshCookie = buildRefreshCookie(newRefreshToken, 7 * 24 * 60 * 60L); |
|
|
|
|
|
|
|
|
ResponseCookie refreshCookie = buildRefreshCookie(newRefreshToken, 7 * 24 * 60 * 60L, request.isSecure()); |
|
|
|
|
|
|
|
|
return ResponseEntity.ok() |
|
|
return ResponseEntity.ok() |
|
|
.header(HttpHeaders.SET_COOKIE, refreshCookie.toString()) |
|
|
.header(HttpHeaders.SET_COOKIE, refreshCookie.toString()) |
|
|
@ -176,7 +178,7 @@ public class AuthController { |
|
|
* 退出登录:清除 refresh token cookie,并使当前 access token 立即失效 |
|
|
* 退出登录:清除 refresh token cookie,并使当前 access token 立即失效 |
|
|
*/ |
|
|
*/ |
|
|
@PostMapping("/logout") |
|
|
@PostMapping("/logout") |
|
|
public ResponseEntity<Map<String, Object>> logout() { |
|
|
|
|
|
|
|
|
public ResponseEntity<Map<String, Object>> logout(HttpServletRequest request) { |
|
|
// 递增 token_version,使已签发的 access token 立即失效(剩余有效期内的令牌不再可用) |
|
|
// 递增 token_version,使已签发的 access token 立即失效(剩余有效期内的令牌不再可用) |
|
|
// 注意:改密/禁用后 token 已失效,SecurityContext 可能为空,此时仅清除 cookie 即可 |
|
|
// 注意:改密/禁用后 token 已失效,SecurityContext 可能为空,此时仅清除 cookie 即可 |
|
|
Authentication auth = SecurityContextHolder.getContext().getAuthentication(); |
|
|
Authentication auth = SecurityContextHolder.getContext().getAuthentication(); |
|
|
@ -189,7 +191,7 @@ public class AuthController { |
|
|
|
|
|
|
|
|
ResponseCookie clearCookie = ResponseCookie.from("refresh_token", "") |
|
|
ResponseCookie clearCookie = ResponseCookie.from("refresh_token", "") |
|
|
.httpOnly(true) |
|
|
.httpOnly(true) |
|
|
.secure(refreshCookieSecure) |
|
|
|
|
|
|
|
|
.secure(refreshCookieSecure && request.isSecure()) |
|
|
.sameSite("Strict") |
|
|
.sameSite("Strict") |
|
|
.path("/auth/refresh") |
|
|
.path("/auth/refresh") |
|
|
.maxAge(0) |
|
|
.maxAge(0) |
|
|
@ -277,10 +279,15 @@ public class AuthController { |
|
|
return userInfo; |
|
|
return userInfo; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
private ResponseCookie buildRefreshCookie(String refreshToken, long maxAgeSeconds) { |
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* 构建 refresh token Cookie。 |
|
|
|
|
|
* Secure 属性按「配置开关 && 当前请求是否 HTTPS」动态决定: |
|
|
|
|
|
* 浏览器仅在 HTTPS 下接受 Secure cookie,本地 HTTP 开发环境必须降级为 false,否则刷新会失效。 |
|
|
|
|
|
*/ |
|
|
|
|
|
private ResponseCookie buildRefreshCookie(String refreshToken, long maxAgeSeconds, boolean isSecureRequest) { |
|
|
return ResponseCookie.from("refresh_token", refreshToken) |
|
|
return ResponseCookie.from("refresh_token", refreshToken) |
|
|
.httpOnly(true) |
|
|
.httpOnly(true) |
|
|
.secure(refreshCookieSecure) |
|
|
|
|
|
|
|
|
.secure(refreshCookieSecure && isSecureRequest) |
|
|
.sameSite("Strict") |
|
|
.sameSite("Strict") |
|
|
.path("/auth/refresh") |
|
|
.path("/auth/refresh") |
|
|
.maxAge(maxAgeSeconds) |
|
|
.maxAge(maxAgeSeconds) |
|
|
|