diff --git a/src/main/java/com/Timo/Timo/global/auth/filter/OriginValidationFilter.java b/src/main/java/com/Timo/Timo/global/auth/filter/OriginValidationFilter.java new file mode 100644 index 00000000..225dd979 --- /dev/null +++ b/src/main/java/com/Timo/Timo/global/auth/filter/OriginValidationFilter.java @@ -0,0 +1,41 @@ +package com.Timo.Timo.global.auth.filter; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.List; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +@Component +public class OriginValidationFilter extends OncePerRequestFilter { + + private static final List PROTECTED_PATHS = List.of( + "/api/v1/auth/reissue", + "/api/v1/auth/logout", + "/api/v1/auth/withdraw" + ); + + @Value("${app.oauth2.allowed-frontend-urls}") + private List allowedFrontendUrls; + + @Override + protected void doFilterInternal( + HttpServletRequest request, HttpServletResponse response, FilterChain filterChain + ) throws ServletException, IOException { + String path = request.getRequestURI(); + + if (PROTECTED_PATHS.stream().anyMatch(path::equals)) { + String origin = request.getHeader("Origin"); + if (origin == null || !allowedFrontendUrls.contains(origin)) { + response.setStatus(HttpServletResponse.SC_FORBIDDEN); + return; + } + } + + filterChain.doFilter(request, response); + } +} diff --git a/src/main/java/com/Timo/Timo/global/auth/utils/CookieUtil.java b/src/main/java/com/Timo/Timo/global/auth/utils/CookieUtil.java index 8f31f74a..8d2a2382 100644 --- a/src/main/java/com/Timo/Timo/global/auth/utils/CookieUtil.java +++ b/src/main/java/com/Timo/Timo/global/auth/utils/CookieUtil.java @@ -6,22 +6,32 @@ public class CookieUtil { public static ResponseCookie createCookie(String name, String value, long maxAgeSeconds, boolean secure) { - return ResponseCookie.from(name, value) + ResponseCookie.ResponseCookieBuilder builder = ResponseCookie.from(name, value) .httpOnly(true) .secure(secure) .path("/api/v1/auth") .maxAge(Duration.ofSeconds(maxAgeSeconds)) - .sameSite("Strict") - .build(); + .sameSite(secure ? "None" : "Strict"); + + if (secure) { + builder.partitioned(true); + } + + return builder.build(); } public static ResponseCookie expireCookie(String name, boolean secure) { - return ResponseCookie.from(name, "") + ResponseCookie.ResponseCookieBuilder builder = ResponseCookie.from(name, "") .httpOnly(true) .secure(secure) .path("/api/v1/auth") .maxAge(0) - .sameSite("Strict") - .build(); + .sameSite(secure ? "None" : "Strict"); + + if (secure) { + builder.partitioned(true); + } + + return builder.build(); } } diff --git a/src/main/java/com/Timo/Timo/global/config/SecurityConfig.java b/src/main/java/com/Timo/Timo/global/config/SecurityConfig.java index f64f6401..902faa58 100644 --- a/src/main/java/com/Timo/Timo/global/config/SecurityConfig.java +++ b/src/main/java/com/Timo/Timo/global/config/SecurityConfig.java @@ -1,5 +1,6 @@ package com.Timo.Timo.global.config; +import com.Timo.Timo.global.auth.filter.OriginValidationFilter; import com.Timo.Timo.global.auth.handler.JwtAuthenticationEntryPoint; import com.Timo.Timo.global.auth.handler.OAuthFailureHandler; import com.Timo.Timo.global.auth.filter.OAuthOriginCaptureFilter; @@ -32,6 +33,7 @@ public class SecurityConfig { private final JwtAuthenticationFilter jwtAuthenticationFilter; private final CorsConfigurationSource corsConfigurationSource; private final OAuthOriginCaptureFilter oAuthOriginCaptureFilter; + private final OriginValidationFilter originValidationFilter; @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { @@ -69,7 +71,8 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti ) .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class) .addFilterBefore(mdcLoggingFilter(), JwtAuthenticationFilter.class) - .addFilterBefore(oAuthOriginCaptureFilter, OAuth2AuthorizationRequestRedirectFilter.class); + .addFilterBefore(oAuthOriginCaptureFilter, OAuth2AuthorizationRequestRedirectFilter.class) + .addFilterBefore(originValidationFilter, JwtAuthenticationFilter.class); return http.build(); } @@ -87,4 +90,14 @@ public FilterRegistrationBean mdcLoggingFilterRegistration( registrationBean.setEnabled(false); return registrationBean; } + + @Bean + public FilterRegistrationBean originValidationFilterRegistration( + OriginValidationFilter originValidationFilter + ) { + FilterRegistrationBean registrationBean = + new FilterRegistrationBean<>(originValidationFilter); + registrationBean.setEnabled(false); + return registrationBean; + } }