fix: harden learning auth flow
This commit is contained in:
@@ -8,7 +8,9 @@ import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.validation.Valid;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseCookie;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
@@ -31,25 +33,30 @@ public class LearningAuthController {
|
||||
}
|
||||
|
||||
@PostMapping("/login")
|
||||
public ApiResponse<Map<String, Object>> login(@Valid @RequestBody LoginRequest request, HttpServletResponse response) {
|
||||
public ResponseEntity<ApiResponse<Map<String, Object>>> login(
|
||||
@Valid @RequestBody LoginRequest request,
|
||||
HttpServletRequest servletRequest,
|
||||
HttpServletResponse response
|
||||
) {
|
||||
if (!(("admin".equals(request.username()) && "admin123".equals(request.password()))
|
||||
|| ("user".equals(request.username()) && "user123".equals(request.password())))) {
|
||||
return new ApiResponse<>(401, "Invalid demo credentials", null, java.time.Instant.now());
|
||||
return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
|
||||
.body(ApiResponse.fail(401, "Invalid demo credentials"));
|
||||
}
|
||||
|
||||
String token = jwtUtil.generateToken(request.username());
|
||||
response.addHeader(HttpHeaders.SET_COOKIE, authCookie(token).toString());
|
||||
return ApiResponse.ok(Map.of(
|
||||
response.addHeader(HttpHeaders.SET_COOKIE, authCookie(token, servletRequest).toString());
|
||||
return ResponseEntity.ok(ApiResponse.ok(Map.of(
|
||||
"token", token,
|
||||
"type", "Bearer",
|
||||
"username", request.username(),
|
||||
"tip", "Attach Authorization: Bearer <token> when calling protected lab APIs or rely on the demo auth cookie for page access."
|
||||
));
|
||||
)));
|
||||
}
|
||||
|
||||
@PostMapping("/logout")
|
||||
public ApiResponse<Map<String, Object>> logout(HttpServletResponse response) {
|
||||
response.addHeader(HttpHeaders.SET_COOKIE, clearAuthCookie().toString());
|
||||
public ApiResponse<Map<String, Object>> logout(HttpServletRequest request, HttpServletResponse response) {
|
||||
response.addHeader(HttpHeaders.SET_COOKIE, clearAuthCookie(request).toString());
|
||||
return ApiResponse.ok(Map.of(
|
||||
"cleared", true,
|
||||
"tip", "Frontend local storage should be cleared too."
|
||||
@@ -67,24 +74,26 @@ public class LearningAuthController {
|
||||
}
|
||||
|
||||
@GetMapping("/introspect")
|
||||
public ApiResponse<Map<String, Object>> introspect(
|
||||
public ResponseEntity<ApiResponse<Map<String, Object>>> introspect(
|
||||
@RequestHeader(value = HttpHeaders.AUTHORIZATION, required = false) String authorization,
|
||||
HttpServletRequest request
|
||||
) {
|
||||
String token = resolveToken(authorization, request);
|
||||
if (!StringUtils.hasText(token)) {
|
||||
return ApiResponse.fail(401, "Missing bearer token");
|
||||
return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
|
||||
.body(ApiResponse.fail(401, "Missing bearer token"));
|
||||
}
|
||||
|
||||
if (!jwtUtil.validate(token)) {
|
||||
return ApiResponse.fail(401, "Token is invalid or expired");
|
||||
return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
|
||||
.body(ApiResponse.fail(401, "Token is invalid or expired"));
|
||||
}
|
||||
|
||||
return ApiResponse.ok(Map.of(
|
||||
return ResponseEntity.ok(ApiResponse.ok(Map.of(
|
||||
"subject", jwtUtil.username(token),
|
||||
"claims", jwtUtil.claims(token),
|
||||
"tip", "JWT is self-contained: the server can read claims without storing a session row for each request."
|
||||
));
|
||||
)));
|
||||
}
|
||||
|
||||
private String resolveToken(String authorization, HttpServletRequest request) {
|
||||
@@ -105,21 +114,28 @@ public class LearningAuthController {
|
||||
return "";
|
||||
}
|
||||
|
||||
private ResponseCookie authCookie(String token) {
|
||||
private ResponseCookie authCookie(String token, HttpServletRequest request) {
|
||||
return ResponseCookie.from(LearningJwtUtil.AUTH_COOKIE_NAME, token)
|
||||
.httpOnly(true)
|
||||
.sameSite("Lax")
|
||||
.secure(isSecureRequest(request))
|
||||
.path("/")
|
||||
.maxAge(Duration.ofMillis(jwtUtil.expirationMillis()))
|
||||
.build();
|
||||
}
|
||||
|
||||
private ResponseCookie clearAuthCookie() {
|
||||
private ResponseCookie clearAuthCookie(HttpServletRequest request) {
|
||||
return ResponseCookie.from(LearningJwtUtil.AUTH_COOKIE_NAME, "")
|
||||
.httpOnly(true)
|
||||
.sameSite("Lax")
|
||||
.secure(isSecureRequest(request))
|
||||
.path("/")
|
||||
.maxAge(Duration.ZERO)
|
||||
.build();
|
||||
}
|
||||
|
||||
private boolean isSecureRequest(HttpServletRequest request) {
|
||||
String forwardedProto = request.getHeader("X-Forwarded-Proto");
|
||||
return request.isSecure() || "https".equalsIgnoreCase(forwardedProto);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user