-
[Spring Security] Form 로그인 시 (username, password) 의 과정Spring 2023. 5. 31. 13:35
이 글은 Spring 공식문서를 참고하여 읽는 흐름대로 작성하였기때문에 문서와는 순서가 다를수있습니다.
Form Login
Spring Security는 HTML 양식을 통해 제공되는 사용자 이름과 암호를 지원합니다. 이 섹션에서는 Spring Security에서 양식 기반 인증이 작동하는 방법에 대해 자세히 설명합니다.
아래 그림은 사용자가 form으로 로그인 했을때의 과정을 보여줍니다.
Redirecting to the Login Page 1) 사용자는 인증되지않은 리소스(/private)에 인증되지 않은 요청을 합니다.
2) 인증되지 않은 요청을 받은 Spring Security의 AuthorizationFilter 는 AccessDeniedException을 유발시켜 해당 요청이 거부가 되었음을 나타냅니다.
3) 사용자가 인증이 되지 않았으므로, ExceptionTranslatonFiler는 설정된 AuthenticationEntryPoint 를 사용하여 LoginUrl로 리디렉션 합니다. 대부분은 AuthenticationEntryPoint 는 LoginUrlAuthenticationEntryPoint. 인스턴스입니다.
4) 브라우저는 리디렉션으로 받은 로그인 페이지를 요청합니다.
5) 로그인 페이지 등장
SecurityFilterChain
SecurityFilterChain은 FilterChainProxy에서 현재 요청에 대해 호출할 Spring Security Filter 인스턴스를 결정하는 데 사용됩니다.
FilterChainProxy
Spring Security의 Servlet 지원이 FilterChainProxy에 포함 되어있습니다.
Spring에서 제공하는 특수 필터로 많은 Filter 인스턴스에 위임할 수 있습니다.
FilterChainProxy 또한 Bean이기 때문에 DelegatingFilterProxy 에 싸여있습니다.
DelegatingFilterProxy
Servlet 컨테이너는 자체 표준을 사용하여 필터 인스턴스를 등록 할 수 있지만, 스프링으로 정의된 빈은 인식하지 못합니다.
때문에 Spring은 Servlet 컨테이너의 라이프사이클과 Spring의 ApplicationContext 간에 브리징을 허용하는 DelegatingFilterProxy라는 필터 구현체를 제공합니다.
표준 서블릿 컨테이너 매커니즘을 통해 DelegatingFilterProxy 를 등록할 수 있지만,
필터로 구현하는 Spring Bean에 모든 작업을 위임합니다.
다음은 DelegatingFilterProxy가 Filter 인스턴스와 FilterChain에 어떻게 적합한지 보여주는 그림입니다.
DelegatingFilterProxy 는 ApplicationContext에서 Bean Filter0을 찾은 다음 Bean Filter0를 호출합니다.
아래는 DelegatingFilterProxy 의 위임 예시입니다.
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) { Filter delegate = getFilterBean(someBeanName); (1) delegate.doFilter(request, response); (2) }
(1) Spring Bean으로 등록된 필터를 Lazily(게으르게:완강하지않게) 가져옵니다.
(해당 Bean이 존재하지않는 경우를 고려하지 않는다라는.. 런타임시에 오류날수있음)
(2) 가져온 Filter Bean으로 작업을 위임합니다.
DelegatingFilterProxy의 또 다른 이점은 Filter bean 인스턴스 조회를 지연할 수 있다는 것입니다.
원래는 컨테이너가 시작되기전에 필터 인스턴스를 등록해야합니다.
그러나 Spring은 ContextLoaderListener를 사용하여 Spring Bean들을 로드하기 때문에
이 작업은 필터 인스턴스가 등록되기 전 까지 수행되지 않습니다.
SecurityFilterChain SecurityFilterChain의 Security Filters는 일반적으로 Beans 입니다. 하지만 DelegatingFilterProxy 대신 FilterChainProxy로 등록 됩니다.
FilterChainProxy 는 Servlet 컨테이너 또는 DelegationgFilterProxy에 직접 등록할 때 많은 이점을 제공합니다.
1. Spring Security의 Servlet 기능의 출발점을 제공하기 때문에, Servlet 기능 문제를 해결하려면 FilterChainProxy에 디버그 지점을 추가하면 된다.
2. Spring Security의 사용의 중심이기 때문에, 선택사항으로 간주되지 않는 작업을 수행할 수 있습니다.
예를 들어, 메모리 누수를 방지하기 위해 SecurityContext를 지우고, HttpFireall을 적용하여 특정 유형의 공격으로부터 어플리케이션을 방어합니다.
3. SecutiryFilterChain이 호출되어야 하는 시기를 결정하는데 있어서 유연함을 제공합니다.
서블릿 컨테이너에서 필터 인스턴스는 URL 기반으로 호출됩니다.
그러나 FilterChainProxy는 RequestMatcher 인터페이스를 사용하여 HttpServletRequest의 모든 항목을 기반으로 호출을 결정할 수 있습니다.
Security Filters
Security Filter는 SecurityFilterChain API를 사용하여 FilterChainProxy에 삽입됩니다.
Filter 인스턴스의 순서가 중요합니다.
일반적으로 Security Filter의 인스턴스 순서를 알 필요는 없지만, 도움이 될 수도 있습니다.
다음은 Security Filter의 목록입니다.
- ForceEagerSessionCreationFilter
- ChannelProcessingFilter
- WebAsyncManagerIntegrationFilter
- SecurityContextPersistenceFilter
- HeaderWriterFilter
- CorsFilter
- CsrfFilter
- LogoutFilter
- OAuth2AuthorizationRequestRedirectFilter
- Saml2WebSsoAuthenticationRequestFilter
- X509AuthenticationFilter
- AbstractPreAuthenticatedProcessingFilter
- CasAuthenticationFilter
- OAuth2LoginAuthenticationFilter
- Saml2WebSsoAuthenticationFilter
- UsernamePasswordAuthenticationFilter
- DefaultLoginPageGeneratingFilter
- DefaultLogoutPageGeneratingFilter
- ConcurrentSessionFilter
- DigestAuthenticationFilter
- BearerTokenAuthenticationFilter
- BasicAuthenticationFilter
- RequestCacheAwareFilter
- SecurityContextHolderAwareRequestFilter
- JaasApiIntegrationFilter
- RememberMeAuthenticationFilter
- AnonymousAuthenticationFilter
- OAuth2AuthorizationCodeGrantFilter
- SessionManagementFilter
- ExceptionTranslationFilter
- AuthorizationFilter
- SwitchUserFilter
A Review of Filters
Spring Security의 Servlet 은 Servlet Filter를 기반으로 제공됩니다. 따라서 Filter에 대해서 알고있으면 좋겠죠?
다음 이미지는 단일 HTTP 요청에 대한 처리의 일반적인 계층화입니다.
FilterChain 1) 클라이언트가 어플리케이션에 요청을 보냅니다.
2) 컨테이너는 요청 URI의 경로를 기반으로 HttpServletRequest 를 처리해야하는 Servlet 과 Filter 인스턴스를 포함하는 FilterChain을 만듭니다. Spring의 경우 Servlet 인스턴스는 DispatcherServlet입니다.
3) 하나의 Servlet이 단일 HttpServletRequest와 HttpServletResponse를 처리할수있습니다.
그러나 두 개 이상의 필터를 사용하여 다음 작업을 수행할 수 있습니다:
- 다운스트림 Filter 인스턴스 또는 Servlet이 호출 되지 않도록 합니다. 이 경우 Filter는 일반적으로 HttpServletResponse를 기록합니다.
- 다운스트림 Filter 인스턴스와 Servlet에서 사용하는 HttpServletRequest 또는 HttpServletResponse를 수정합니다.
Filter의 힘은 Filter에 전달되는 FilterChain에서 나옵니다.
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) { // 어플리케이션에 전달되기 전에 chain.doFilter(request, response); // 어플리케이션 호출 // 어플리케이션에 전달된 후에 }
Filter는 다운스트림 Filter 인스턴스와 Servlet에만 영향을 미치므로 각 필터가 호출되는 순서가 매우 중요합니다.
다음 이미지는 여러 SecurityFilterChain 인스턴스를 보여줍니다:
Multiple SecurityFilterChain 위 그림에서 FilterChainProxy는 사용할 SecurityFilterChain을 결정합니다.
가장 먼저 일치하는 SecurityFilterChain만 호출 됩니다.
만약, /api/messages/ URL이 요청 되면, /api/**의 SecurityFilterChain0 패턴과 일치합니다.
그렇기때문에 SecurityFilterChainn 또한 일치하지만 SecurityFilterChain0가 호출이 됩니다.
/messages/ URL이 요청되면 SecurityFilterChain0 패턴과 일치하지 않으므로 FilterChainProxy 는 계속해서 SecurityFilterChain과 매칭을 이어갑니다.
결국에는 이외의 다른 SecurityFilterChain과 매칭이 이루어지지 않으면
마지막 /**의 SecurityFilterChainn 패턴과 일치하게 되며 호출이 됩니다.
SecurityFilterChain0 에는 3개, SecurityFilterChainn에는 4개의 Filter 인스턴스가 구성이 되어있습니다.
각 SecurityFilterChain은 고유하고, 별도로 구성할 수 있다는 점을 알고있어야합니다.
실제로 어플리케이션에서 Spring Security가 특정 요청을 무시하도록 할때, SecurityFilterChain에 Filter 인스턴스가 없을수 있습니다.
SecurityFilterChain diagram
username password authentication Form으로 username과 password를 입력하여 로그인 시에
UsernamePasswordAuthenticationFilter가 username과 password를 검증하여 인증을 진행합니다.
해당 필터는 AbstractAuthenticationProcessingFilter 를 상속 받습니다.
AbstractAuthenticationProcessingFilter
사용자의 자격 증명을 인증하는 기본 필터로 사용합니다.
인증 정보를 인증하기 전에 Spring Security는 일반적으로 AuthenticationEntryPoint를 사용하여 인증 정보를 요청합니다.
AuthenticationEntryPoint
클라이언트에서 자격 증명을 요청하는 HTTP 응답을 보내는 데 사용됩니다.
인증 예외가 발생 할 경우에 대한 이후 액션에 대한 기능입니다.
만약 클라이언트가 액세스 권한이 없는 리소스에 인증되지않은 요청을 하는경우
해당 인터페이스를 구현하여 로그인페이지로 리디렉션을 하던가, 응답을 내려주던가 등의 작업이 가능합니다.
AbstractAuthenticationProcessingFilter는 제출된 모든 인증 요청을 인증할 수 있습니다
1) 사용자가 username, password 등의 정보로 자격 증명을 제출하면
AbstractAuthenticationProcessingFilter는 인증할 HttpServletRequest에서 Authentication 객체를 생성합니다.
작성된 Authentication 객체의 유형은 AbstractAuthenticationProcessingFilter의 하위 클래스에 따라 다릅니다.
예를 들어, username/password 기반 인증으로 자격 증명을 제출한 경우 UsernamePasswordAuthenticationFilter 가 사용되며
Authentication 를 상속받는 UsernamePasswordAuthenticationToken 객체가 생성됩니다.
2) 생성된 Authentication 객체는 AuthenticationManager 로 전달됩니다.
AuthenticationManager
Spring Security의 필터가 인증을 수행하는 방법을 정의하는 API입니다.
AuthenticationManager를 호출한 컨트롤러(즉, Spring Security의 Filters 인스턴스)에 의해 SecurityContextHolder에 설정되어 인증이 반환됩니다.
AuthenticationManager의 구현은 무엇이든 가능하지만 가장 일반적인 구현은 ProviderManager입니다.ProviderManager
ProviderManager에 여러 AuthenticationProvider 인스턴스를 주입 할 수 있습니다.
AuthenticationProvider
각각의 AuthenticationProvider 는 특정 유형의 인증을 수행합니다.
예를 들어, DaoAuthenticationProvider는 username/password 기반 인증을 지원하는 반면 JwtAuthenticationProvider는 JWT 토큰 인증을 지원합니다.
각 AuthenticationProvider 는 인증이 성공, 실패, 또는 결정을 내릴 수 없음의 방식으로 나타낼 수 있습니다.
실패 인 경우 (전달된 인증 유형이 구성된 AuthenticationProvider 목록 중 지원되는 인증 유형이 구성되지 않은 경우) ProviderNotFoundException 예외와 함께 인증이 실패합니다.
실제로 각 AuthenticationProvider는 특정 유형의 인증을 수행하는 방법을 알고 있습니다.
예를 들어, 한 AuthenticationProvider는 사용자 이름/암호의 유효성을 검사할 수 있고
다른 AuthenticationProvider는 SAML assertion 을 인증할 수 있습니다.
따라서 AuthenticationManager 단 하나의 빈으로 여러 유형의 인증을 지원 및 수행이 가능합니다
또한 AuthenticationProvider가 인증을 수행할 수 없는 경우 ProviderManager를 사용하여 선택적 상위 AuthenticationManager를 구성할 수 있습니다. 부모는 모든 유형의 AuthenticationManager일 수 있지만 종종 ProviderManager의 인스턴스입니다.
실제로, 여러개의 ProviderManager 인스턴스는 동일한 상위 AuthenticationManager를 공유할 수 있습니다.
이는 공통 인증 (동일한 상위 AuthenticationManager 공유) 뿐만 아니라 서로 다른 인증 매커니즘을 가진 여러 Security Filter Chain 인스턴스가 있는 시나리오에서는 일반적입니다.
기본적으로 ProviderManager는 성공적인 인증 요청에 의해 반환되는 인증 개체에서 중요한 자격 증명 정보를 지우려고 시도합니다. 이렇게 하면 비밀번호와 같은 정보가 HttpSession에서 필요 이상으로 오래 유지되지 않습니다.
이로 인해서 캐시에서 사용자 정보를 사용할때 문제가 발생하기 때문에 고려를 해야합니다.
(캐시에서 UserDetail 인스턴스와 같은 개체를 참조하거나, 자격증명이 제거된 경우)
비저장 어플리케이션(stateless application)에서는 성능을 향상 시킬수 있습니다.
비저장 어플리케이션(stateless application)
한 세션에서 생성된 클라이언트 데이터를 해당 클라이언트와의 다음 세션에서 사용하기 위해 저장하지 않는 응용 프로그램입니다.이와 같은 캐시 정보에 대한 문제를 해결하기 위한 방법은
반환된 인증 객체를 생성하는 AuthenticationProvider나, 캐시 구현체에서 개체의 복사본을 만드는것입니다.
또는 ProviderManager의 요소에서 reraseCredentialsAfterAuthentication 을 disable 처리 하는것입니다.
3) 인증이 되지않은 경우, 실패입니다.
'Spring' 카테고리의 다른 글
공통 메서드 설계 Static vs Spring Bean (0) 2024.12.26 Swagger + Spring Security 적용 시 No API definition provided. 오류 (0) 2023.09.02 Maria DB 10.4.12 - Charset, Collation latin1에서 utf8mb4로 변경하기 (0) 2023.08.16