Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SAMBAD-220] 클라이언트 기반 Redirect URL 설정 기능 구현 #85

Merged
merged 1 commit into from
Aug 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,19 +1,23 @@
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;
import org.depromeet.sambad.moring.auth.infrastructure.SecurityProperties;
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;
Expand All @@ -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);
}
Expand All @@ -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"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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;

Expand Down Expand Up @@ -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();
}
}
Original file line number Diff line number Diff line change
@@ -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<String> 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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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())
Expand Down
Loading