From d5aee1a6d9d23db94dba30ba94e1187966d060c3 Mon Sep 17 00:00:00 2001 From: MinJunKKang Date: Thu, 14 Aug 2025 15:02:10 +0900 Subject: [PATCH] =?UTF-8?q?=EB=A7=89=EA=B5=AC=EB=A7=89=EA=B5=AC=20?= =?UTF-8?q?=EC=9E=91=EC=97=85=ED=95=98=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../canfly/global/config/SecurityConfig.java | 11 ++-- .../auth/jwt/FrontRedirectCaptureFilter.java | 49 +++++++++++++++++ .../auth/utils/OAuth2SuccessHandler.java | 53 ++++++++++++------- 3 files changed, 90 insertions(+), 23 deletions(-) create mode 100644 src/main/java/hackerthon/likelion13th/canfly/login/auth/jwt/FrontRedirectCaptureFilter.java diff --git a/src/main/java/hackerthon/likelion13th/canfly/global/config/SecurityConfig.java b/src/main/java/hackerthon/likelion13th/canfly/global/config/SecurityConfig.java index 3ce374f..89c7161 100644 --- a/src/main/java/hackerthon/likelion13th/canfly/global/config/SecurityConfig.java +++ b/src/main/java/hackerthon/likelion13th/canfly/global/config/SecurityConfig.java @@ -1,6 +1,7 @@ package hackerthon.likelion13th.canfly.global.config; import hackerthon.likelion13th.canfly.login.auth.jwt.AuthCreationFilter; +import hackerthon.likelion13th.canfly.login.auth.jwt.FrontRedirectCaptureFilter; import hackerthon.likelion13th.canfly.login.auth.jwt.JwtValidationFilter; import hackerthon.likelion13th.canfly.login.auth.utils.OAuth2SuccessHandler; import hackerthon.likelion13th.canfly.login.auth.utils.OAuth2UserServiceImpl; @@ -11,9 +12,9 @@ import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer; import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.access.intercept.AuthorizationFilter; -import org.springframework.security.web.authentication.AnonymousAuthenticationFilter; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.CorsConfigurationSource; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; @@ -31,6 +32,7 @@ public class SecurityConfig { private final JwtValidationFilter jwtValidationFilter; private final OAuth2UserServiceImpl oAuth2UserService; private final OAuth2SuccessHandler oAuth2SuccessHandler; + private final FrontRedirectCaptureFilter frontRedirectCaptureFilter; @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { @@ -45,11 +47,9 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti "/health", "/swagger-ui/**", "/v3/api-docs/**", - "/oauth2/authorization/kakao", - "/login/oauth2/code/**", + "/oauth2/**", // ๐ŸŸก ์นด์นด์˜ค OAuth ๋ฆฌ๋””๋ ‰์…˜ + "/login/oauth2/**", // ๐ŸŸก ์นด์นด์˜ค OAuth ์ฝœ๋ฐฑ "/token/**", - "/oauth/**", - "/token/return", "/index.html", "/users/me", "/users/logout", @@ -68,6 +68,7 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti .sessionManagement(session -> session .sessionCreationPolicy(SessionCreationPolicy.STATELESS) ) + .addFilterBefore(frontRedirectCaptureFilter, OAuth2AuthorizationRequestRedirectFilter.class) .addFilterBefore(authCreationFilter, AuthorizationFilter.class) .addFilterBefore(jwtValidationFilter, AuthCreationFilter.class); diff --git a/src/main/java/hackerthon/likelion13th/canfly/login/auth/jwt/FrontRedirectCaptureFilter.java b/src/main/java/hackerthon/likelion13th/canfly/login/auth/jwt/FrontRedirectCaptureFilter.java new file mode 100644 index 0000000..a94d00c --- /dev/null +++ b/src/main/java/hackerthon/likelion13th/canfly/login/auth/jwt/FrontRedirectCaptureFilter.java @@ -0,0 +1,49 @@ +package hackerthon.likelion13th.canfly.login.auth.jwt; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpSession; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; + +@Slf4j +@Component +public class FrontRedirectCaptureFilter extends OncePerRequestFilter { + + public static final String ATTR = "FRONT_REDIRECT_URI"; + public static final String PARAM = "front_redirect"; + + @Override + protected void doFilterInternal( + HttpServletRequest request, + HttpServletResponse response, + FilterChain chain + ) throws ServletException, IOException { + + // ์ธ๊ฐ€ ์‹œ์ž‘์ ์œผ๋กœ ๋“ค์–ด์˜ค๋Š” ์š”์ฒญ์—์„œ๋งŒ ์บก์ฒ˜ + String path = request.getServletPath(); // ex) /oauth2/authorization/kakao + if (path != null && path.startsWith("/oauth2/authorization/")) { + String redirect = request.getParameter(PARAM); + if (redirect == null) { + // ํ˜น์‹œ ํ”„๋ก ํŠธ๊ฐ€ ์•„์ง redirect_uri๋ฅผ ์“ฐ๊ณ  ์žˆ๋‹ค๋ฉด ๋ฐฑ์—…์œผ๋กœ๋„ ๋ฐ›์ž + redirect = request.getParameter("redirect_uri"); + } + if (redirect == null) { + // ํ—ค๋”๋กœ ๋ณด๋ƒˆ๋‹ค๋ฉด ์ด๊ฒƒ๋„ ๋ฐฑ์—… + redirect = request.getHeader("X-Front-Redirect"); + } + if (redirect != null && !redirect.isBlank()) { + HttpSession session = request.getSession(true); + session.setAttribute(ATTR, redirect); + log.info("[FRONT_REDIRECT] saved into session: {}", redirect); + } + } + + chain.doFilter(request, response); + } +} \ No newline at end of file diff --git a/src/main/java/hackerthon/likelion13th/canfly/login/auth/utils/OAuth2SuccessHandler.java b/src/main/java/hackerthon/likelion13th/canfly/login/auth/utils/OAuth2SuccessHandler.java index 59253a9..d4ffd96 100644 --- a/src/main/java/hackerthon/likelion13th/canfly/login/auth/utils/OAuth2SuccessHandler.java +++ b/src/main/java/hackerthon/likelion13th/canfly/login/auth/utils/OAuth2SuccessHandler.java @@ -5,6 +5,7 @@ import hackerthon.likelion13th.canfly.login.service.UserService; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpSession; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; @@ -18,6 +19,9 @@ import java.io.IOException; import java.util.List; +import static hackerthon.likelion13th.canfly.login.auth.jwt.FrontRedirectCaptureFilter.ATTR; +import static hackerthon.likelion13th.canfly.login.auth.jwt.FrontRedirectCaptureFilter.PARAM; + @Slf4j @Component @RequiredArgsConstructor @@ -29,15 +33,6 @@ public class OAuth2SuccessHandler implements AuthenticationSuccessHandler { @Value("${frontend.base-url}") private String defaultRedirect; - /** ํ—ˆ์šฉ URI ํ™”์ดํŠธ๋ฆฌ์ŠคํŠธ */ - private List getAuthorizedUris() { - return List.of( - defaultRedirect, - "http://localhost:3000", - "http://localhost:3000/" - ); - } - @Override public void onAuthenticationSuccess( HttpServletRequest request, @@ -47,10 +42,10 @@ public void onAuthenticationSuccess( /* 1. OAuth2User ์ถ”์ถœ */ OAuth2User oAuth2User = (OAuth2User) authentication.getPrincipal(); - String provider = oAuth2User.getAttribute("provider"); // kakao - String providerId = oAuth2User.getAttribute("id"); // 4351993247 - String nickname = oAuth2User.getAttribute("name"); // ๊ฐ•๋ฏผ์ค€ - String email = oAuth2User.getAttribute("email"); + String provider = oAuth2User.getAttribute("provider"); // kakao + String providerId = oAuth2User.getAttribute("id"); // 4351993247 + String nickname = oAuth2User.getAttribute("name"); // ๊ฐ•๋ฏผ์ค€ + String email = oAuth2User.getAttribute("email"); /* 2. username ํ˜•์‹: {kakao}๊ฐ•๋ฏผ์ค€ */ String username = String.format("{%s}%s", provider, nickname); @@ -74,17 +69,39 @@ public void onAuthenticationSuccess( /* 4. JWT ์ฆ‰์‹œ ๋ฐœ๊ธ‰ (providerId ๊ธฐ์ค€) */ JwtDto jwt = userService.jwtMakeSave(providerId); log.info("๐Ÿ”‘ JWT ๋ฐœ๊ธ‰ ์™„๋ฃŒ | providerId={}", providerId); - /* 5. ํ”„๋กœํ•„ ๋ฏธ์™„๋ฃŒ ์—ฌ๋ถ€(needsProfile) ๊ณ„์‚ฐ */ boolean needsProfile = userService.needsProfile(providerId); - /* 6. redirect_uri ๊ฒ€์ฆ */ - String frontRedirect = request.getParameter("redirect_uri"); - if (frontRedirect == null || !getAuthorizedUris().contains(frontRedirect)) { + HttpSession session = request.getSession(false); + String frontRedirect = null; + if (session != null) { + frontRedirect = (String) session.getAttribute(ATTR); + session.removeAttribute(ATTR); // ์ผํšŒ์„ฑ + log.info("[FRONT_REDIRECT] loaded from session: {}", frontRedirect); + } + + // ํ˜น์‹œ ์„ธ์…˜์ด ๋น„์–ด์žˆ๋‹ค๋ฉด ํŒŒ๋ผ๋ฏธํ„ฐ/ํ—ค๋” ๋ฐฑ์—… + if (frontRedirect == null) { + frontRedirect = request.getParameter(PARAM); + if (frontRedirect == null) { + frontRedirect = request.getParameter("redirect_uri"); + } + if (frontRedirect == null) { + frontRedirect = request.getHeader("X-Front-Redirect"); + } + } + + // ํ™”์ดํŠธ๋ฆฌ์ŠคํŠธ(์ •ํ™• ๋งค์นญ) โ€” ํ•„์š” ์‹œ ์ •๊ทœํ™” ๋กœ์ง ์ถ”๊ฐ€ + List allow = List.of( + defaultRedirect, + "http://localhost:3000", + "http://localhost:3000/" + ); + if (frontRedirect == null || !allow.contains(frontRedirect)) { + log.warn("[FRONT_REDIRECT] not allowed or missing โ†’ fallback: {}", defaultRedirect); frontRedirect = defaultRedirect; } - /* 7. accessToken + needsProfile๋กœ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ */ String redirectUrl = UriComponentsBuilder.fromUriString(frontRedirect) .queryParam("accessToken", jwt.getAccessToken()) .queryParam("needsProfile", needsProfile)