implement on frontend
This commit is contained in:
@@ -1,10 +1,12 @@
|
||||
package com.ddf.vodsystem.configuration;
|
||||
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.Customizer;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.authentication.HttpStatusEntryPoint;
|
||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
@@ -24,7 +26,7 @@ public class SecurityConfig {
|
||||
|
||||
/**
|
||||
* Builds the security filter chain: stateless sessions, CSRF disabled, the {@link JwtFilter}
|
||||
* installed, and per-endpoint authorization rules.
|
||||
* installed, {@code 401} for unauthenticated requests, and per-endpoint authorization rules.
|
||||
*
|
||||
* @param http the HTTP security builder to configure
|
||||
* @return the built {@link SecurityFilterChain}
|
||||
@@ -41,11 +43,14 @@ public class SecurityConfig {
|
||||
.requestMatchers("/api/v1/media/compress").permitAll() // compression available without authenticated
|
||||
.requestMatchers("/api/v1/jobs/**").permitAll() // same with jobs
|
||||
.requestMatchers("/api/v1/media/**").authenticated()
|
||||
.requestMatchers("/api/v1/users/me", "/api/v1/users/logout").authenticated()
|
||||
.requestMatchers("/api/v1/users/login").permitAll()
|
||||
.requestMatchers("/api/v1/users/me").authenticated()
|
||||
// authenticated by the refresh token cookie, not the (possibly expired) access token
|
||||
.requestMatchers("/api/v1/users/login", "/api/v1/users/refresh", "/api/v1/users/logout").permitAll()
|
||||
.requestMatchers("/api/v1/stream/start", "/api/v1/stream/stop", "/api/v1/stream/heartbeat").permitAll()
|
||||
.anyRequest().authenticated()
|
||||
)
|
||||
// 401 rather than the default 403, so clients know to refresh their session
|
||||
.exceptionHandling(e -> e.authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED)))
|
||||
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
|
||||
);
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.ddf.vodsystem.controllers;
|
||||
|
||||
import com.ddf.vodsystem.dto.APIResponse;
|
||||
import com.ddf.vodsystem.controllers.dto.SessionResponse;
|
||||
import com.ddf.vodsystem.controllers.dto.Token;
|
||||
import com.ddf.vodsystem.dto.TokenPackage;
|
||||
import com.ddf.vodsystem.dto.properties.AuthProperties;
|
||||
@@ -64,18 +65,18 @@ public class UserController {
|
||||
* @param token the payload carrying the Google ID token to verify
|
||||
* @param request the incoming request, used to decide the cookies' {@code Secure} flag
|
||||
* @param response the response the cookies are written to
|
||||
* @return {@code 200 OK} with no data
|
||||
* @return {@code 200 OK} wrapping the access token lifetime as a {@link SessionResponse}
|
||||
* @throws NotAuthenticated if the Google ID token is rejected
|
||||
*/
|
||||
@PostMapping("/login")
|
||||
public ResponseEntity<APIResponse<Void>> login(@RequestBody Token token,
|
||||
public ResponseEntity<APIResponse<SessionResponse>> login(@RequestBody Token token,
|
||||
HttpServletRequest request,
|
||||
HttpServletResponse response) {
|
||||
TokenPackage tokenPackage = userService.login(token.token());
|
||||
addCookies(request, response, tokenPackage.accessToken(), tokenPackage.refreshToken());
|
||||
|
||||
return ResponseEntity.ok(
|
||||
new APIResponse<>(SUCCESS, "Logged in successfully", null)
|
||||
new APIResponse<>(SUCCESS, "Logged in successfully", sessionResponse())
|
||||
);
|
||||
}
|
||||
|
||||
@@ -85,11 +86,11 @@ public class UserController {
|
||||
* @param refreshToken the refresh token from the {@code refreshToken} cookie, if present
|
||||
* @param request the incoming request, used to decide the cookies' {@code Secure} flag
|
||||
* @param response the response the new cookies are written to
|
||||
* @return {@code 200 OK} with no data
|
||||
* @return {@code 200 OK} wrapping the new access token lifetime as a {@link SessionResponse}
|
||||
* @throws NotAuthenticated if the refresh token is missing or rejected
|
||||
*/
|
||||
@PostMapping("/refresh")
|
||||
public ResponseEntity<APIResponse<Void>> refresh(
|
||||
public ResponseEntity<APIResponse<SessionResponse>> refresh(
|
||||
@CookieValue(name = "refreshToken", required = false) String refreshToken,
|
||||
HttpServletRequest request,
|
||||
HttpServletResponse response) {
|
||||
@@ -97,7 +98,7 @@ public class UserController {
|
||||
addCookies(request, response, tokenPackage.accessToken(), tokenPackage.refreshToken());
|
||||
|
||||
return ResponseEntity.ok(
|
||||
new APIResponse<>(SUCCESS, "Refreshed tokens successfully", null)
|
||||
new APIResponse<>(SUCCESS, "Refreshed tokens successfully", sessionResponse())
|
||||
);
|
||||
}
|
||||
|
||||
@@ -123,6 +124,10 @@ public class UserController {
|
||||
);
|
||||
}
|
||||
|
||||
private SessionResponse sessionResponse() {
|
||||
return new SessionResponse(jwtExpiration.getSeconds());
|
||||
}
|
||||
|
||||
private void addCookies(
|
||||
HttpServletRequest request,
|
||||
HttpServletResponse response,
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.ddf.vodsystem.controllers.dto;
|
||||
|
||||
/**
|
||||
* Session metadata returned after login or refresh. The tokens themselves are only ever sent as
|
||||
* HttpOnly cookies; this tells the client when to refresh them.
|
||||
*
|
||||
* @param accessTokenExpiresIn seconds until the access token cookie expires
|
||||
*/
|
||||
public record SessionResponse(
|
||||
long accessTokenExpiresIn
|
||||
) {}
|
||||
@@ -53,7 +53,7 @@ public class JwtFilter extends OncePerRequestFilter {
|
||||
Cookie[] cookies = request.getCookies();
|
||||
if (cookies != null) {
|
||||
jwt = Arrays.stream(cookies)
|
||||
.filter(cookie -> "token".equals(cookie.getName()))
|
||||
.filter(cookie -> "accessToken".equals(cookie.getName()))
|
||||
.map(Cookie::getValue)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
|
||||
Reference in New Issue
Block a user