From 638b5396b8c13d91281b9d493a96d5f09063aa01 Mon Sep 17 00:00:00 2001 From: Kijun Kwon <39583312+kkjsw17@users.noreply.github.com> Date: Sun, 4 Aug 2024 06:47:22 +0900 Subject: [PATCH] =?UTF-8?q?[SAMBAD-220]=20=ED=81=B4=EB=9D=BC=EC=9D=B4?= =?UTF-8?q?=EC=96=B8=ED=8A=B8=20=EA=B8=B0=EB=B0=98=20Redirect=20URL=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84=20?= =?UTF-8?q?(#85)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../OAuth2LoginSuccessHandler.java | 30 ++++++++++-- .../auth/application/TokenInjector.java | 14 +++++- .../auth/presentation/JwtTokenFilter.java | 8 ++- .../auth/presentation/RedirectUrlFilter.java | 49 +++++++++++++++++++ .../moring/common/config/SecurityConfig.java | 4 ++ 5 files changed, 94 insertions(+), 11 deletions(-) create mode 100644 src/main/java/org/depromeet/sambad/moring/auth/presentation/RedirectUrlFilter.java diff --git a/src/main/java/org/depromeet/sambad/moring/auth/application/OAuth2LoginSuccessHandler.java b/src/main/java/org/depromeet/sambad/moring/auth/application/OAuth2LoginSuccessHandler.java index 198d8fac..12cf34ee 100644 --- a/src/main/java/org/depromeet/sambad/moring/auth/application/OAuth2LoginSuccessHandler.java +++ b/src/main/java/org/depromeet/sambad/moring/auth/application/OAuth2LoginSuccessHandler.java @@ -1,8 +1,11 @@ package org.depromeet.sambad.moring.auth.application; +import static org.depromeet.sambad.moring.auth.presentation.RedirectUrlFilter.*; import static org.depromeet.sambad.moring.auth.presentation.exception.AuthExceptionCode.*; import java.io.IOException; +import java.util.Arrays; +import java.util.Objects; import org.depromeet.sambad.moring.auth.domain.CustomOAuth2User; import org.depromeet.sambad.moring.auth.domain.LoginResult; @@ -10,10 +13,11 @@ import org.depromeet.sambad.moring.auth.presentation.exception.AlreadyRegisteredUserException; import org.depromeet.sambad.moring.meeting.member.application.MeetingMemberService; import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; +import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; @@ -35,7 +39,7 @@ public void onAuthenticationSuccess( try { LoginResult result = resolveLoginResultFromAuthentication(authentication); tokenInjector.injectTokensToCookie(result, response); - redirectToSuccessUrl(result, response); + redirectToSuccessUrl(result, request, response); } catch (AlreadyRegisteredUserException e) { handleAlreadyExistUser(response); } @@ -46,12 +50,28 @@ private LoginResult resolveLoginResultFromAuthentication(Authentication authenti return authService.handleLoginSuccess(oAuth2User.getAuthAttributes()); } - private void redirectToSuccessUrl(LoginResult result, HttpServletResponse response) throws IOException { - String redirectUrl = determineRedirectUrl(result); + private void redirectToSuccessUrl( + LoginResult result, HttpServletRequest request, HttpServletResponse response + ) throws IOException { + String redirectUrlByCookie = getRedirectUrlByCookie(request); + String redirectUrl = determineRedirectUrl(result, redirectUrlByCookie); response.sendRedirect(redirectUrl); + tokenInjector.invalidateCookie(REDIRECT_URL_COOKIE_NAME, response); + } + + private String getRedirectUrlByCookie(HttpServletRequest request) { + return Arrays.stream(request.getCookies()) + .filter(cookie -> Objects.equals(cookie.getName(), REDIRECT_URL_COOKIE_NAME)) + .findFirst() + .map(Cookie::getValue) + .orElse(null); } - private String determineRedirectUrl(LoginResult result) { + private String determineRedirectUrl(LoginResult result, String redirectCookie) { + if (StringUtils.hasText(redirectCookie)) { + return redirectCookie; + } + if (meetingMemberService.isNotEnterAnyMeeting(result.userId())) { return result.isNewUser() ? securityProperties.newUserRedirectUrl() + "?newUser=true" diff --git a/src/main/java/org/depromeet/sambad/moring/auth/application/TokenInjector.java b/src/main/java/org/depromeet/sambad/moring/auth/application/TokenInjector.java index 6073c09b..06998964 100644 --- a/src/main/java/org/depromeet/sambad/moring/auth/application/TokenInjector.java +++ b/src/main/java/org/depromeet/sambad/moring/auth/application/TokenInjector.java @@ -27,7 +27,7 @@ public void injectTokensToCookie(LoginResult result, HttpServletResponse respons addCookie(REFRESH_TOKEN, result.refreshToken(), refreshTokenMaxAge, response); } - private void addCookie(String name, String value, int maxAge, HttpServletResponse response) { + public void addCookie(String name, String value, int maxAge, HttpServletResponse response) { Cookie cookie = new Cookie(name, value); cookie.setPath("/"); cookie.setMaxAge(maxAge); @@ -38,4 +38,16 @@ private void addCookie(String name, String value, int maxAge, HttpServletRespons response.addCookie(cookie); } + + public void invalidateCookie(String name, HttpServletResponse response) { + Cookie cookie = new Cookie(name, null); + cookie.setPath("/"); + cookie.setMaxAge(0); + cookie.setHttpOnly(securityProperties.cookie().httpOnly()); + cookie.setDomain(securityProperties.cookie().domain()); + cookie.setSecure(securityProperties.cookie().secure()); + cookie.setAttribute("SameSite", "None"); + + response.addCookie(cookie); + } } diff --git a/src/main/java/org/depromeet/sambad/moring/auth/presentation/JwtTokenFilter.java b/src/main/java/org/depromeet/sambad/moring/auth/presentation/JwtTokenFilter.java index 714c6120..b4ebe2ab 100644 --- a/src/main/java/org/depromeet/sambad/moring/auth/presentation/JwtTokenFilter.java +++ b/src/main/java/org/depromeet/sambad/moring/auth/presentation/JwtTokenFilter.java @@ -5,6 +5,7 @@ import java.io.IOException; import org.depromeet.sambad.moring.auth.application.RefreshTokenService; +import org.depromeet.sambad.moring.auth.application.TokenInjector; import org.depromeet.sambad.moring.auth.domain.TokenResolver; import org.depromeet.sambad.moring.auth.presentation.exception.AuthenticationRequiredException; import org.depromeet.sambad.moring.auth.presentation.exception.RefreshTokenNotValidaException; @@ -19,7 +20,6 @@ import io.jsonwebtoken.ExpiredJwtException; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; -import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; @@ -31,6 +31,7 @@ public class JwtTokenFilter extends OncePerRequestFilter { private final TokenResolver tokenResolver; + private final TokenInjector tokenInjector; private final UserDetailsService userDetailsService; private final RefreshTokenService refreshTokenService; @@ -87,10 +88,7 @@ private void setAuthentication(HttpServletRequest request, UserDetails userDetai } private void invalidateCookie(String cookieName, HttpServletResponse response) { - Cookie cookieForInvalidate = new Cookie(cookieName, null); - cookieForInvalidate.setMaxAge(0); - response.addCookie(cookieForInvalidate); - + tokenInjector.invalidateCookie(cookieName, response); SecurityContextHolder.clearContext(); } } diff --git a/src/main/java/org/depromeet/sambad/moring/auth/presentation/RedirectUrlFilter.java b/src/main/java/org/depromeet/sambad/moring/auth/presentation/RedirectUrlFilter.java new file mode 100644 index 00000000..ee1907ea --- /dev/null +++ b/src/main/java/org/depromeet/sambad/moring/auth/presentation/RedirectUrlFilter.java @@ -0,0 +1,49 @@ +package org.depromeet.sambad.moring.auth.presentation; + +import java.io.IOException; +import java.util.List; + +import org.depromeet.sambad.moring.auth.application.TokenInjector; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; +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 lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@Component +public class RedirectUrlFilter extends OncePerRequestFilter { + + private final TokenInjector tokenInjector; + + public static final String REDIRECT_URL_QUERY_PARAM = "redirectUrl"; + public static final String REDIRECT_URL_COOKIE_NAME = "redirect_url"; + private static final List REDIRECT_URL_INJECTION_PATTERNS = List.of( + "/oauth2/authorization/.*" + ); + + @Override + protected void doFilterInternal( + HttpServletRequest request, HttpServletResponse response, FilterChain filterChain + ) throws ServletException, IOException { + if (isRedirectRequest(request)) { + tokenInjector.invalidateCookie(REDIRECT_URL_COOKIE_NAME, response); + String redirectUri = request.getParameter(REDIRECT_URL_QUERY_PARAM); + + if (StringUtils.hasText(redirectUri)) { + tokenInjector.addCookie(REDIRECT_URL_COOKIE_NAME, redirectUri, 3600, response); + } + } + + filterChain.doFilter(request, response); + } + + private boolean isRedirectRequest(HttpServletRequest request) { + return REDIRECT_URL_INJECTION_PATTERNS.stream() + .anyMatch(request.getRequestURI()::matches); + } +} diff --git a/src/main/java/org/depromeet/sambad/moring/common/config/SecurityConfig.java b/src/main/java/org/depromeet/sambad/moring/common/config/SecurityConfig.java index 6eafcc2c..f0bf533d 100644 --- a/src/main/java/org/depromeet/sambad/moring/common/config/SecurityConfig.java +++ b/src/main/java/org/depromeet/sambad/moring/common/config/SecurityConfig.java @@ -6,11 +6,13 @@ import org.depromeet.sambad.moring.auth.infrastructure.SecurityProperties; import org.depromeet.sambad.moring.auth.presentation.JwtTokenFilter; +import org.depromeet.sambad.moring.auth.presentation.RedirectUrlFilter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService; +import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter; import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.AuthenticationFailureHandler; @@ -28,6 +30,7 @@ public class SecurityConfig { private final DefaultOAuth2UserService defaultOAuth2UserService; private final AuthenticationEntryPoint authenticationEntryPoint; private final JwtTokenFilter jwtTokenFilter; + private final RedirectUrlFilter redirectUrlFilter; private final SecurityProperties securityProperties; private final AuthenticationSuccessHandler authenticationSuccessHandler; private final AuthenticationFailureHandler authenticationFailureHandler; @@ -94,6 +97,7 @@ private void configureContentSecurityPolicy(HttpSecurity http) throws Exception } private void configureOAuth2Login(HttpSecurity http) throws Exception { + http.addFilterBefore(redirectUrlFilter, OAuth2AuthorizationRequestRedirectFilter.class); http.addFilterBefore(jwtTokenFilter, UsernamePasswordAuthenticationFilter.class); http.oauth2Login(oauth2 -> oauth2.loginPage(securityProperties.loginUrl())