Spring/Spring Security

[Spring Security] AuthenticationSuccessHandler에 대하여

Woong이 2024. 3. 15. 00:23
반응형

개요

Spring Security가 제공하는 AuthenticationSuccessHandler는 인증이 성공할 경우 구현하여 적용이 가능한 인터페이스이다. 이번 글에서는 이 기능이 어떻게 작동하는지 알아보고 Spring에서 제공하는 해당 인터페이스의 구현체들은 어떤 것들이 있는지 알아보려고 한다.

 


AuthenticationSuccessHandler란?

public interface AuthenticationSuccessHandler {
    default void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authentication) throws IOException, ServletException {
        this.onAuthenticationSuccess(request, response, authentication);
        chain.doFilter(request, response);
    }

    void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException;
}

 

위의 코드처럼 AuthenticationSuccessHandler는 아주 간단하게 onAuthenticationSuccess라는 메서드만 제공한다. 

 

우리가 보통 Spring Security를 도입하면 SecurityFilterChain을 Spring Bean으로 등록하게 되는데, 이 과정에서 인증 성공 시 특정한 로직을 적용하고 싶다면 아래와 같이 AuthenticationSuccessHandler를 SecurityFilterChain에 적용하면 된다.

@RequiredArgsConstructor
@EnableWebSecurity
@Configuration
public class SecurityConfig {

    // 생략

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {

    	// 예: OAuth2 로그인에 AuthenticationSuccessHandler 적용
        http.oauth2Login(login -> login
        		.successHandler(authenticationSuccessHandler)
        )

    	// 생략
    }
 }

 

이렇게 하면 로그인 성공 후 인증 완료 시 위에서 말한 AuthenticationSuccessHandler의 onAuthenticationSuccess 메서드가 실행되어 해당 메서드에 구현한 로직이 실행된다.

 


AuthenticationSuccessHandler 구현체

Spring은 AuthenticationSuccessHandler 인터페이스를 구현한 여러 구현체들을 제공한다.

 

ForwardAuthenticationSuccessHandler

public class ForwardAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
    private final String forwardUrl;

    public ForwardAuthenticationSuccessHandler(String forwardUrl) {
        Assert.isTrue(UrlUtils.isValidRedirectUrl(forwardUrl), () -> {
            return "'" + forwardUrl + "' is not a valid forward URL";
        });
        this.forwardUrl = forwardUrl;
    }

    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        request.getRequestDispatcher(this.forwardUrl).forward(request, response);
    }
}

이 구현체는 단순하게 인증에 성공할 경우, 객체 생성 시 생성자로 넘긴 url을 foward하는 기능을 제공한다. 해당 구현체의 onAuthenticationSuccess 메서드를 보면 HttpServletRequest의 RequestDispatcher를 통해 forward를 실행하는 것을 확인할 수 있다.

 

SimpleUrlAuthenticationSuccessHandler

public class SimpleUrlAuthenticationSuccessHandler extends AbstractAuthenticationTargetUrlRequestHandler implements AuthenticationSuccessHandler {
    public SimpleUrlAuthenticationSuccessHandler() {
    }

    public SimpleUrlAuthenticationSuccessHandler(String defaultTargetUrl) {
        setDefaultTargetUrl(defaultTargetUrl);
    }

    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        handle(request, response, authentication);
        clearAuthenticationAttributes(request);
    }

    protected final void clearAuthenticationAttributes(HttpServletRequest request) {
        HttpSession session = request.getSession(false);
        if (session != null) {
            session.removeAttribute("SPRING_SECURITY_LAST_EXCEPTION");
        }

    }
}

이 구현체는 인증에 성공하면 사용자를 targetUrl으로 redirect 시키는 기능을 제공한다. 이 구현체는 AbstractAuthenticationTargetUrlRequestHandler라는 추상 클래스도 상속받는데, onAuthenticationSuccess 메서드를 확인해보면 상속받은 클래스의 handle 메서드를 통해 아래와 같이 요청을 redirect한다.

// AbstractAuthenticationTargetUrlRequestHandler
protected void handle(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
			throws IOException, ServletException {
    String targetUrl = determineTargetUrl(request, response, authentication);
    if (response.isCommitted()) {
        this.logger.debug(LogMessage.format("Did not redirect to %s since response already committed.", targetUrl));
        return;
    }
    this.redirectStrategy.sendRedirect(request, response, targetUrl);
}

 

Redirect를 한 이후에는 onAuthenticationSuccess 메서드로 돌아와, clearAuthenticationAttributes라는 메서드를 통해 남아있는 session data를 모두 제거한다.

 

SavedRequestAwareAuthenticationSuccessHandler

public class SavedRequestAwareAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {

    protected final Log logger = LogFactory.getLog(this.getClass());

    private RequestCache requestCache = new HttpSessionRequestCache();

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
            Authentication authentication) throws ServletException, IOException {
        SavedRequest savedRequest = this.requestCache.getRequest(request, response);
        if (savedRequest == null) {
            super.onAuthenticationSuccess(request, response, authentication);
            return;
        }
        String targetUrlParameter = getTargetUrlParameter();
        if (isAlwaysUseDefaultTargetUrl()
                || (targetUrlParameter != null && StringUtils.hasText(request.getParameter(targetUrlParameter)))) {
            this.requestCache.removeRequest(request, response);
            super.onAuthenticationSuccess(request, response, authentication);
            return;
        }
        clearAuthenticationAttributes(request);
        // Use the DefaultSavedRequest URL
        String targetUrl = savedRequest.getRedirectUrl();
        getRedirectStrategy().sendRedirect(request, response, targetUrl);
    }

    public void setRequestCache(RequestCache requestCache) {
        this.requestCache = requestCache;
    }

}

이 클래스는 위에서 설명한 SimpleUrlAuthenticationSuccessHandler를 상속받는다. 이 클래스는 조금 특별한 기능을 제공하는데, 바로 요청을 RequestCache라는 객체에 저장하는 기능이다.

 

그리고, 해당 클래스는 onAuthenticationSuccess 메서드에서 다양한 상황에 따라 redirect할 url을 결정한다.

  • isAlwaysUseDefaultTargetUrl 메서드가 true를 반환할 경우, AbstractAuthenticationTargetUrlRequestHandler 추상 클래스의 defaultTargetUrl로 redirect를 진행하고 session에 저장된 DefaultSavedRequest가 있다면 제거된다.
  • targetUrlParameter가 request에 설정되어 있는 경우, 해당 url로 redirect를 진행하고 session에 저장된 DefaultSavedRequest가 있다면 제거된다.
  • RequestCache에 저장되어 있는 SavedRequest 객체를 조회하고 존재할 경우, 해당 객체의 getRedirectUrl 메서드를 통해  찾은 url로 redirect를 진행한다. 이 경우, RequestCache에는 해당 요청이 그대로 저장된 상태로 진행된다.
  • 만약 저장되어 있는 요청이 없을 경우, 상위 클래스(SimpleUrlAuthenticationSuccessHandler)에서 처리하도록 위임한다.

 

참고로 AuthenticationSuccessHandler 설정을 가능하게 해주는 추상 클래스인 AbstractAuthenticationFilterConfigurer를 살펴보면, defaultSuccessHandler를 SavedRequestAwareAuthenticationSuccessHandler를 사용하도록 지정해두고 별도의 AuthenticationSuccessHandler가 등록되어 있지 않았을 경우에 이를 사용하도록 설정한 것을 확인할 수 있다.

public abstract class AbstractAuthenticationFilterConfigurer<B extends HttpSecurityBuilder<B>, T extends AbstractAuthenticationFilterConfigurer<B, T, F>, F extends AbstractAuthenticationProcessingFilter> extends AbstractHttpConfigurer<T, B> {
    
    // 생략
    
    private SavedRequestAwareAuthenticationSuccessHandler defaultSuccessHandler = new SavedRequestAwareAuthenticationSuccessHandler();
    
    // 생략
 }

 


마무리

Spring Security가 제공하는 여러 AuthenticationSuccessHandler 구현체가 있다는 것을 이번 기회에 알 수 있었다. 이 구현체들을 선택해 사용하거나 상속받아서 직접 핸들러를 제작하면 프로젝트의 상황에 맞게 인증 성공 로직을 구현할 수 있을 것이다.

 

다음에는 인증에 실패할 경우 실행되는 AuthenticationFailureHandler에 대해서 알아볼 것이다.

 


반응형