From dd5d11051cf43eea0ee903d12b78f30de2ccd8b7 Mon Sep 17 00:00:00 2001 From: rlatjddms Date: Wed, 19 Aug 2026 15:55:38 +0900 Subject: [PATCH] =?UTF-8?q?[fix]=20#173=20JWT=20API=20CSRF=20=EB=B9=84?= =?UTF-8?q?=ED=99=9C=EC=84=B1=ED=99=94=20=EB=B0=8F=20exceptionHandling=20?= =?UTF-8?q?=EC=A4=91=EB=B3=B5=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/deploy.yml | 2 +- .../security/config/CsrfCookieFilter.java | 23 -------- .../security/config/SecurityConfig.java | 54 +++++++------------ 3 files changed, 19 insertions(+), 60 deletions(-) delete mode 100644 src/main/java/com/chaewookim/accountbookformoms/global/security/config/CsrfCookieFilter.java diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 509c0e5..a51cdf2 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -1,7 +1,7 @@ name: Deploy to OCI on: push: - branches: [ dev ] + branches: [ fix/ACC-173-security-csrf ] concurrency: group: deploy-${{ github.ref }} diff --git a/src/main/java/com/chaewookim/accountbookformoms/global/security/config/CsrfCookieFilter.java b/src/main/java/com/chaewookim/accountbookformoms/global/security/config/CsrfCookieFilter.java deleted file mode 100644 index 846a0f2..0000000 --- a/src/main/java/com/chaewookim/accountbookformoms/global/security/config/CsrfCookieFilter.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.chaewookim.accountbookformoms.global.security.config; - -import jakarta.servlet.FilterChain; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import org.springframework.security.web.csrf.CsrfToken; -import org.springframework.web.filter.OncePerRequestFilter; - -import java.io.IOException; - -public class CsrfCookieFilter extends OncePerRequestFilter { - - @Override - protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { - CsrfToken csrfToken = (CsrfToken) request.getAttribute(CsrfToken.class.getName()); - if (csrfToken != null) { - // Render the token value to a cookie by causing the deferred token to be loaded - csrfToken.getToken(); - } - filterChain.doFilter(request, response); - } -} diff --git a/src/main/java/com/chaewookim/accountbookformoms/global/security/config/SecurityConfig.java b/src/main/java/com/chaewookim/accountbookformoms/global/security/config/SecurityConfig.java index 80ddece..098b275 100644 --- a/src/main/java/com/chaewookim/accountbookformoms/global/security/config/SecurityConfig.java +++ b/src/main/java/com/chaewookim/accountbookformoms/global/security/config/SecurityConfig.java @@ -22,10 +22,6 @@ import org.springframework.web.cors.CorsConfigurationSource; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; -import org.springframework.security.web.csrf.CookieCsrfTokenRepository; -import org.springframework.security.web.csrf.CsrfTokenRequestAttributeHandler; -import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; - import jakarta.servlet.http.HttpServletResponse; import java.util.List; @@ -52,19 +48,8 @@ public PasswordEncoder passwordEncoder() { // 시큐리티 필터 체인 @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - CookieCsrfTokenRepository tokenRepository = CookieCsrfTokenRepository.withHttpOnlyFalse(); - CsrfTokenRequestAttributeHandler requestHandler = new CsrfTokenRequestAttributeHandler(); - http - // CSRF 방어 활성화 (CookieCsrfTokenRepository 사용) - .csrf(csrf -> csrf - .csrfTokenRepository(tokenRepository) - .csrfTokenRequestHandler(requestHandler) - ) .cors(cors -> cors.configurationSource(corsConfigurationSource())) - - // CsrfCookieFilter 등록 - .addFilterAfter(new CsrfCookieFilter(), BasicAuthenticationFilter.class) // JWT 사용 예정이기 때문에 폼 로그인 & HTTP Basic 인증 비활성화 .formLogin(AbstractHttpConfigurer::disable) .httpBasic(AbstractHttpConfigurer::disable) @@ -116,23 +101,6 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // 그 외 모든 요청은 인증 필요 .anyRequest().authenticated() ) - // API 요청은 로그인 페이지로 리다이렉트하지 않고 JSON으로 응답 - .exceptionHandling(exception -> exception - .authenticationEntryPoint((request, response, authException) -> { - response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); - response.setContentType("application/json;charset=UTF-8"); - response.getWriter().write( - "{\"success\":false,\"data\":null,\"error\":\"인증이 필요합니다.\"}" - ); - }) - .accessDeniedHandler((request, response, accessDeniedException) -> { - response.setStatus(HttpServletResponse.SC_FORBIDDEN); - response.setContentType("application/json;charset=UTF-8"); - response.getWriter().write( - "{\"success\":false,\"data\":null,\"error\":\"접근 권한이 없습니다.\"}" - ); - }) - ) // OAuth2 로그인 설정 .oauth2Login(oauth -> oauth .authorizationEndpoint(endpoint -> endpoint @@ -145,15 +113,29 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { ) // JWT 필터 추가 .addFilterBefore(new JwtFilter(jwtTokenProvider), UsernamePasswordAuthenticationFilter.class) + // API 요청은 로그인 페이지로 리다이렉트하지 않고 JSON으로 응답 .exceptionHandling(exception -> exception .authenticationEntryPoint((request, response, authException) -> { if (request.getRequestURI().startsWith("/api/")) { - response.sendError(jakarta.servlet.http.HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized"); - } else { - response.sendRedirect("/login"); + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + response.setContentType("application/json;charset=UTF-8"); + response.getWriter().write( + "{\"success\":false,\"data\":null,\"error\":\"인증이 필요합니다.\"}" + ); + return; } + response.sendRedirect("/login"); }) - ); + .accessDeniedHandler((request, response, accessDeniedException) -> { + response.setStatus(HttpServletResponse.SC_FORBIDDEN); + response.setContentType("application/json;charset=UTF-8"); + response.getWriter().write( + "{\"success\":false,\"data\":null,\"error\":\"접근 권한이 없습니다.\"}" + ); + }) + ) + // oauth2Login 이후에 끄지 않으면 CSRF가 다시 활성화될 수 있음 + .csrf(AbstractHttpConfigurer::disable); return http.build(); }