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..df1db6e1 --- /dev/null +++ b/src/main/java/gg/agit/konect/global/auth/filter/OAuth2RedirectUriSaveFilter.java @@ -0,0 +1,62 @@ +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; + +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 (isValidOriginOnlyRedirect(redirectUri)) { + HttpSession session = request.getSession(true); + session.setAttribute(REDIRECT_URI_SESSION_KEY, redirectUri); + } + } + + 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; + } + } +} 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; +} 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