From 91561c33f16b752557939141d8130d2d2a0b16f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EB=8F=99=ED=9B=88?= <2dh2@naver.com> Date: Thu, 15 Jan 2026 00:01:07 +0900 Subject: [PATCH 1/3] =?UTF-8?q?chore:=20=EC=84=9C=EB=B8=8C=EB=AA=A8?= =?UTF-8?q?=EB=93=88=20=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/config b/src/main/resources/config index bdc1a8c9..bef49114 160000 --- a/src/main/resources/config +++ b/src/main/resources/config @@ -1 +1 @@ -Subproject commit bdc1a8c9b444010c2f6f7047855b09602b7aa58d +Subproject commit bef49114523ea91253677495e24dba28b808d007 From 3ec974b564a2405ae91b87e947cf2fc8d974f4b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EB=8F=99=ED=9B=88?= <2dh2@naver.com> Date: Thu, 15 Jan 2026 00:02:50 +0900 Subject: [PATCH 2/3] =?UTF-8?q?feat:=20OAuth=20=EB=A1=9C=EA=B7=B8=EC=9D=B8?= =?UTF-8?q?=20=ED=9B=84=20=EB=A6=AC=EB=8B=A4=EC=9D=B4=EB=A0=89=ED=8A=B8=20?= =?UTF-8?q?=EA=B2=BD=EB=A1=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../filter/OAuth2RedirectUriSaveFilter.java | 36 ++++++++++++++++ .../handler/OAuth2LoginSuccessHandler.java | 41 +++++++++++++++++-- .../konect/global/config/SecurityConfig.java | 9 ++++ .../global/config/SecurityProperties.java | 18 ++++++++ 4 files changed, 101 insertions(+), 3 deletions(-) create mode 100644 src/main/java/gg/agit/konect/global/auth/filter/OAuth2RedirectUriSaveFilter.java create mode 100644 src/main/java/gg/agit/konect/global/config/SecurityProperties.java diff --git a/src/main/java/gg/agit/konect/global/auth/filter/OAuth2RedirectUriSaveFilter.java b/src/main/java/gg/agit/konect/global/auth/filter/OAuth2RedirectUriSaveFilter.java new file mode 100644 index 00000000..41fa7904 --- /dev/null +++ b/src/main/java/gg/agit/konect/global/auth/filter/OAuth2RedirectUriSaveFilter.java @@ -0,0 +1,36 @@ +package gg.agit.konect.global.auth.filter; + +import java.io.IOException; + +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpSession; + +@Component +public class OAuth2RedirectUriSaveFilter extends OncePerRequestFilter { + + public static final String REDIRECT_URI_SESSION_KEY = "redirect_uri"; + + @Override + protected void doFilterInternal( + HttpServletRequest request, + HttpServletResponse response, + FilterChain filterChain + ) throws ServletException, IOException { + + if (request.getRequestURI().startsWith("/oauth2/authorization/")) { + String redirectUri = request.getParameter("redirect_uri"); + if (redirectUri != null && !redirectUri.isBlank()) { + HttpSession session = request.getSession(true); + session.setAttribute(REDIRECT_URI_SESSION_KEY, redirectUri); + } + } + + filterChain.doFilter(request, response); + } +} diff --git a/src/main/java/gg/agit/konect/global/auth/handler/OAuth2LoginSuccessHandler.java b/src/main/java/gg/agit/konect/global/auth/handler/OAuth2LoginSuccessHandler.java index fc5fb07d..d15df3f2 100644 --- a/src/main/java/gg/agit/konect/global/auth/handler/OAuth2LoginSuccessHandler.java +++ b/src/main/java/gg/agit/konect/global/auth/handler/OAuth2LoginSuccessHandler.java @@ -1,8 +1,10 @@ package gg.agit.konect.global.auth.handler; import java.io.IOException; +import java.net.URI; import java.util.Map; import java.util.Optional; +import java.util.Set; import org.springframework.beans.factory.annotation.Value; import org.springframework.security.core.Authentication; @@ -11,11 +13,12 @@ import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.stereotype.Component; -import gg.agit.konect.global.code.ApiResponseCode; -import gg.agit.konect.global.exception.CustomException; import gg.agit.konect.domain.user.enums.Provider; import gg.agit.konect.domain.user.model.User; import gg.agit.konect.domain.user.repository.UserRepository; +import gg.agit.konect.global.code.ApiResponseCode; +import gg.agit.konect.global.config.SecurityProperties; +import gg.agit.konect.global.exception.CustomException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpSession; @@ -31,6 +34,7 @@ public class OAuth2LoginSuccessHandler implements AuthenticationSuccessHandler { private static final int TEMP_SESSION_EXPIRATION_SECONDS = 600; private final UserRepository userRepository; + private final SecurityProperties securityProperties; @Override public void onAuthenticationSuccess( @@ -76,7 +80,10 @@ private void sendLoginSuccessResponse( HttpSession session = request.getSession(true); session.setAttribute("userId", user.getId()); - response.sendRedirect(frontendBaseUrl + "/home"); + String redirectUri = (String) session.getAttribute("redirect_uri"); + session.removeAttribute("redirect_uri"); + + response.sendRedirect(resolveSafeRedirect(redirectUri)); } private String extractEmail(OAuth2User oauthUser, Provider provider) { @@ -92,4 +99,32 @@ private String extractEmail(OAuth2User oauthUser, Provider provider) { return (String)current; } + + private String resolveSafeRedirect(String redirectUri) { + if (redirectUri == null || redirectUri.isBlank()) { + return frontendBaseUrl + "/home"; + } + + Set allowedOrigins = securityProperties.getAllowedRedirectOrigins(); + + try { + URI uri = URI.create(redirectUri); + + if (uri.getScheme() == null || uri.getHost() == null) { + return frontendBaseUrl + "/home"; + } + + String origin = uri.getScheme() + "://" + uri.getHost() + portPart(uri); + + if (allowedOrigins.contains(origin)) { + return redirectUri; + } + } catch (Exception ignored) {} + + return frontendBaseUrl + "/home"; + } + + private String portPart(URI uri) { + return (uri.getPort() == -1) ? "" : ":" + uri.getPort(); + } } diff --git a/src/main/java/gg/agit/konect/global/config/SecurityConfig.java b/src/main/java/gg/agit/konect/global/config/SecurityConfig.java index b79e4eaf..c7d45d51 100644 --- a/src/main/java/gg/agit/konect/global/config/SecurityConfig.java +++ b/src/main/java/gg/agit/konect/global/config/SecurityConfig.java @@ -12,13 +12,17 @@ import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer; +import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter; import org.springframework.security.web.SecurityFilterChain; +import gg.agit.konect.global.auth.filter.OAuth2RedirectUriSaveFilter; import gg.agit.konect.global.auth.handler.OAuth2LoginSuccessHandler; import gg.agit.konect.global.auth.oauth.SocialOAuthService; +import lombok.RequiredArgsConstructor; @Configuration @EnableWebSecurity +@RequiredArgsConstructor public class SecurityConfig { @Value("${app.frontend.base-url}") @@ -30,6 +34,9 @@ public class SecurityConfig { @Autowired private OAuth2LoginSuccessHandler oAuth2LoginSuccessHandler; + @Autowired + private OAuth2RedirectUriSaveFilter redirectUriSaveFilter; + @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http @@ -59,6 +66,8 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { }) ); + http.addFilterBefore(redirectUriSaveFilter, OAuth2AuthorizationRequestRedirectFilter.class); + return http.build(); } } diff --git a/src/main/java/gg/agit/konect/global/config/SecurityProperties.java b/src/main/java/gg/agit/konect/global/config/SecurityProperties.java new file mode 100644 index 00000000..0680f74f --- /dev/null +++ b/src/main/java/gg/agit/konect/global/config/SecurityProperties.java @@ -0,0 +1,18 @@ +package gg.agit.konect.global.config; + +import java.util.Set; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@Component +@ConfigurationProperties(prefix = "app") +public class SecurityProperties { + + private Set allowedRedirectOrigins; +} From 23cdb2c93c6c979a3842cb273bd5a56a90da17d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EB=8F=99=ED=9B=88?= <2dh2@naver.com> Date: Thu, 15 Jan 2026 00:19:11 +0900 Subject: [PATCH 3/3] =?UTF-8?q?feat:=20redirect=5Furi=EB=A5=BC=20origin?= =?UTF-8?q?=EB=A7=8C=20=ED=97=88=EC=9A=A9=ED=95=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EC=A0=9C=ED=95=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../filter/OAuth2RedirectUriSaveFilter.java | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/src/main/java/gg/agit/konect/global/auth/filter/OAuth2RedirectUriSaveFilter.java b/src/main/java/gg/agit/konect/global/auth/filter/OAuth2RedirectUriSaveFilter.java index 41fa7904..df1db6e1 100644 --- a/src/main/java/gg/agit/konect/global/auth/filter/OAuth2RedirectUriSaveFilter.java +++ b/src/main/java/gg/agit/konect/global/auth/filter/OAuth2RedirectUriSaveFilter.java @@ -1,6 +1,7 @@ package gg.agit.konect.global.auth.filter; import java.io.IOException; +import java.net.URI; import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; @@ -25,7 +26,8 @@ protected void doFilterInternal( if (request.getRequestURI().startsWith("/oauth2/authorization/")) { String redirectUri = request.getParameter("redirect_uri"); - if (redirectUri != null && !redirectUri.isBlank()) { + + if (isValidOriginOnlyRedirect(redirectUri)) { HttpSession session = request.getSession(true); session.setAttribute(REDIRECT_URI_SESSION_KEY, redirectUri); } @@ -33,4 +35,28 @@ protected void doFilterInternal( filterChain.doFilter(request, response); } + + private boolean isValidOriginOnlyRedirect(String redirectUri) { + if (redirectUri == null || redirectUri.isBlank()) { + return false; + } + + try { + URI uri = URI.create(redirectUri); + + if (uri.getScheme() == null || uri.getHost() == null) { + return false; + } + + if ((uri.getPath() != null && !uri.getPath().isEmpty()) + || uri.getQuery() != null + || uri.getFragment() != null) { + return false; + } + + return true; + } catch (Exception e) { + return false; + } + } }