Spring Security에서 로그인 페이지나 에러 페이지와 같은 기본 페이지를 필터에서 응답하는 이유는 Spring Security의 구조와 동작 방식 때문입니다. 구체적으로는, Spring Security가 요청을 필터 체인에서 먼저 처리하고, 보안 관련 요청을 컨트롤러 레이어까지 전달하지 않기 위해 기본 페이지를 필터에서 응답합니다.
DefaultLoginPageGeneratingFilter는 이러한 보안 관련 기본 페이지를 생성하고 응답하는 역할을 수행하는 Spring Security의 필터입니다.
왜 필터에서 기본 페이지를 응답하는가?
- Spring Security의 아키텍처:
- Spring Security는 필터 기반 보안 구조로 설계되어 있습니다. 요청이 서버에 도달하면, Spring Security는 SecurityFilterChain을 통해 보안 관련 요청을 먼저 필터링하고 필요한 보안 검사를 수행합니다.
- 인증이 필요한 요청에 대해 컨트롤러까지 전달하지 않고 필터 체인 내에서 요청을 처리하여 응답을 생성하는 것이 일반적입니다. 예를 들어, 로그인 페이지 요청이나 인증 실패 응답 등은 보안과 관련된 요청이므로, 이를 필터에서 처리하는 것이 Spring Security의 설계 방식에 부합합니다.
- 보안 관련 기본 페이지의 독립성:
- 기본 로그인 페이지나 에러 페이지는 보안과 관련된 기능으로, Spring Security가 기본적으로 제공하는 기능입니다. 이러한 기본 페이지는 보안 구성을 별도로 커스텀하지 않아도 기본적으로 작동하도록 설계되어 있습니다.
- 이 기본 페이지들이 컨트롤러 레이어에 의존하지 않고 보안 필터 단에서 독립적으로 동작할 수 있도록, Spring Security는 이러한 페이지 생성을 필터에서 처리합니다.
- 의존성 분리와 커스터마이징 용이성:
- 만약 Spring Security가 기본 페이지를 컨트롤러에서 제공한다면, 사용자가 이를 커스터마이징할 때 기본 컨트롤러와 커스텀 컨트롤러 간의 충돌이 발생할 수 있습니다.
- Spring Security는 DefaultLoginPageGeneratingFilter를 통해 기본 로그인 페이지를 생성하고, 이를 필터 단에서 응답함으로써, 사용자가 별도로 컨트롤러에서 로그인 페이지를 커스터마이징할 수 있도록 의존성을 분리합니다.
- 결과적으로, 사용자는 DefaultLoginPageGeneratingFilter를 비활성화하고 자신의 커스텀 컨트롤러를 만들어 로그인 페이지를 제공할 수 있습니다.
- 미리 정의된 기본 페이지 제공:
- DefaultLoginPageGeneratingFilter는 Spring Security가 기본으로 제공하는 로그인 페이지입니다. 사용자가 로그인 페이지를 정의하지 않은 경우, Spring Security는 이 필터를 통해 기본적인 HTML 로그인 페이지를 생성하여 응답합니다.
- 이렇게 필터에서 기본 페이지를 제공하면, 사용자는 로그인 페이지를 추가로 정의할 필요가 없으며, Spring Security는 최소한의 설정으로 기본 보안 페이지를 제공할 수 있습니다.
DefaultLoginPageGeneratingFilter의 역할
- DefaultLoginPageGeneratingFilter는 Spring Security가 제공하는 기본 로그인 페이지를 생성하는 필터입니다. 사용자가 로그인 페이지를 별도로 정의하지 않으면, 이 필터는 간단한 HTML 로그인 페이지를 생성하여 응답합니다.
- 사용자가 커스텀 로그인 페이지를 정의한 경우, DefaultLoginPageGeneratingFilter는 비활성화됩니다. 이로써 사용자가 로그인 페이지를 쉽게 커스터마이징할 수 있도록 유연성을 제공합니다.
요약
- 필터에서 기본 페이지를 응답하는 이유는 Spring Security가 필터 기반 보안 구조로 설계되어 있고, 보안 관련 기본 페이지를 필터 단에서 처리함으로써 보안 관련 요청을 컨트롤러 레이어까지 전달하지 않기 위해서입니다.
- 컨트롤러에 기본 페이지가 존재하지 않도록 설계함으로써, 사용자가 기본 페이지와 충돌 없이 로그인 페이지나 에러 페이지를 커스터마이징할 수 있도록 의존성을 분리한 것입니다.
- DefaultLoginPageGeneratingFilter는 Spring Security의 기본 로그인 페이지를 제공하는 필터로, 사용자가 커스텀 로그인 페이지를 정의하지 않은 경우에만 작동합니다.
이 방식은 Spring Security의 보안 설정을 보다 유연하고 쉽게 확장할 수 있도록 도와줍니다.
DefaultLogoutPageGeneratingFilter
이 필터는 DefaultSecurityFilterChain에 기본적으로 등록되는 필터.
이 필터는 Get: /logout 경로에 대해 기본 로그아웃 페이지를 응답하는 역할을 수행한다.
이 필터는 여러 로그인 설정에 의존되며 가장 많이 사용하는 formLogin에서는 커스텀 SecurityFilterChain 등록시 아래와 같은 설정을 통해 사용할 수 있다.
// 기본 사용
http
.formLogin(Customizer.withDefaults());
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
if (this.matcher.matches(request)) {
renderLogout(request, response);
}
else {
if (logger.isTraceEnabled()) {
logger.trace(LogMessage.format("Did not render default logout page since request did not match [%s]",
this.matcher));
}
filterChain.doFilter(request, response);
}
}
GenericFilterBean과 OncePerRequestFilter의 차이
- GenericFilterBean:
- GenericFilterBean은 Spring의 모든 요청에 대해 실행되는 필터입니다.
- 서블릿 필터를 상속하여 Spring 환경에서 사용할 수 있도록 지원하는 기본 필터 클래스이며, Spring Security 외부에서도 일반적으로 사용할 수 있습니다.
- doFilter 메서드를 오버라이드하여 필터링 작업을 수행하며, 포워딩(forward)나 인클루드(include) 요청이 있어도 계속 호출됩니다. 즉, 같은 요청이 다른 서블릿으로 포워드될 때도 여러 번 실행될 수 있습니다.
- 일반적으로 모든 요청에 대해 일관된 처리를 해야 하는 상황에 적합합니다.
- OncePerRequestFilter:
- OncePerRequestFilter는 한 번의 요청에 대해 한 번만 실행되는 필터입니다.
- GenericFilterBean을 확장한 클래스로, 포워딩이나 인클루드가 발생하더라도 한 요청당 한 번만 실행되도록 보장합니다.
- 이는 내부적으로 요청 속성에 플래그를 설정하여, 같은 요청에 대해 한 번만 필터가 실행되도록 제어합니다.
- 로그아웃과 같이 요청당 한 번만 처리해야 하는 작업에 적합합니다.
로그인 페이지와 로그아웃 페이지에 각각 다른 필터가 사용된 이유
- 로그인 페이지(DefaultLoginPageGeneratingFilter)가 GenericFilterBean 기반인 이유:
- 로그인 페이지 생성 필터는 모든 요청을 검사하여 사용자가 로그인하지 않았을 경우 기본 로그인 페이지를 응답해야 합니다.
- 로그인 페이지는 단일 요청이 여러 리소스로 전달되는 경우에도 일관된 로그인 페이지를 제공해야 하므로, GenericFilterBean을 기반으로 구현하여 포워딩이나 인클루드가 발생해도 계속 실행되도록 설계되었습니다.
- 예를 들어, 특정 리소스에 접근할 때 인증이 필요하면, 이 필터는 해당 요청에 대해 기본 로그인 페이지를 제공해야 하므로 포워딩된 요청에서도 로그인 페이지가 제공될 수 있도록 구현된 것입니다.
- 로그아웃 페이지(LogoutFilter)가 OncePerRequestFilter 기반인 이유:
- 로그아웃 요청은 한 번의 요청으로 로그아웃 작업을 완료해야 하므로, 포워딩이나 인클루드가 있을 때 중복으로 처리되지 않도록 하는 것이 중요합니다.
- OncePerRequestFilter를 기반으로 구현함으로써, 로그아웃 요청이 들어오면 해당 요청에 대해 한 번만 로그아웃 작업을 수행하도록 보장합니다.
- 따라서 같은 요청이 여러 리소스로 포워드되거나 인클루드되는 경우에도, 로그아웃 작업이 중복으로 실행되지 않도록 설계되었습니다.
GenericFilter와 GenericFilterBean의 차이
- GenericFilter:
- GenericFilter는 서블릿 API에서 제공하는 기본 필터 인터페이스로, Spring Security에서는 사용되지 않습니다.
- javax.servlet.Filter 인터페이스를 상속하여 서블릿 환경에서만 사용할 수 있습니다.
- Spring에서의 추가적인 기능은 제공하지 않으므로, Spring 기반 애플리케이션보다는 전통적인 서블릿 환경에서 사용됩니다.
- GenericFilterBean:
- GenericFilterBean은 Spring에서 제공하는 기본 필터 클래스로, Spring 환경에서 사용하는 서블릿 필터를 위한 추상 클래스입니다.
- GenericFilterBean은 GenericFilter를 상속받지 않고, 대신 javax.servlet.Filter 인터페이스를 직접 구현하여 Spring에서 필요한 초기화, 종료 메서드 및 설정 기능을 추가했습니다.
- doFilter 메서드를 구현하여 필터링 작업을 수행하며, Spring의 Bean 라이프사이클을 지원합니다.
요약
- GenericFilterBean은 모든 요청에 대해 필터를 적용하고자 할 때 적합하며, 포워딩이나 인클루드된 요청에서도 계속 실행됩니다.
- 로그인 페이지를 제공하는 DefaultLoginPageGeneratingFilter는 모든 요청에 대해 로그인 페이지를 제공할 수 있어야 하므로 GenericFilterBean을 기반으로 합니다.
- OncePerRequestFilter는 한 요청당 한 번만 실행되어야 할 때 적합하며, 포워딩이나 인클루드된 요청에서 중복 실행되지 않습니다.
- 로그아웃 작업을 수행하는 LogoutFilter는 한 번의 로그아웃 요청으로만 로그아웃 작업을 처리해야 하므로 OncePerRequestFilter를 기반으로 합니다.
이를 통해 Spring Security는 각 필터가 해야 하는 역할에 맞는 적합한 필터 클래스를 선택하여 사용하고 있습니다.