보안이 적용된 웹 요청의 일반적인 흐름
- 사용자가 보호된 리소스를 요청한다.
- 인증 관리자(컴포넌트)가 사용자의 크리덴션(Credential, 증명 수단)을 요청한다. ex) 패스워드
- 인증 관리자에게 크리덴션을 제공한다.
- 인증 관리자는 크리덴션 저장소에서 사용자의 크리덴션을 조회한다.
- 인증 관리자는 사용자가 제공한 크리덴션과 크리덴셜 저장소의 크리덴션을 비교해 검증한다.
- 유효한 크리덴션이 아니면 Exception을 throw 한다.
- 유효하다면, 접근 결정 관리자(컴포넌트)는 사용자가 적절한 권한을 가지고 있는지 검증한다.
- 적절한 권한을 부여받지 못한 사용자라면 Exception을 throw 한다.
- 권한이 있는 사용자라면 보호된 리소스의 접근을 허용한다.
서블릿 필터와 필터 체인
Spring Security는 서블릿 필터를 기반으로 서블릿을 지원한다. 서블릿 필터는 애플리케이션의 엔드포인트에 요청이 도달하기 전에 가로채서 어떤 처리(전처리)를 할 수 있는 적절한 포인트를 제공한다. 응답을 클라이언트에 전달하기 전에 어떤 처리(후처리)를 할 수도 있다. 1
클라이언트가 어플리케이션으로 요청을 보내면 컨테이너는 Servlet과 여러 Filter로 구성된 FilterChain을 만들어서 요청 URI path를 기반으로 HttpServletRequest를 처리한다. Spring Security는 대략 25개의 필터를 제공하며, 이 필터들은 조건적으로 적용할 수 있고 Filter 인터페이스를 직접 구성해서 추가할 수도 있다.
단일 요청과 응답 처리는 한 개의 Servlet이 담당하지만 Filter는 여러 개를 사용할 수 있다. Filter는 나머지 Filter와 Servlet에만 영향을 주기 때문에 Filter의 실행 순서는 중요하다. 2
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
// 전처리
chain.doFilter(request, response);
// 후처리
}
💟 DelegatingFilterProxy
스프링 부트는 DelegatingFilterProxy라는 Filter 구현체로 서블릿 필터와 스프링의 ApplicationContext를 연결한다. 표준 서블릿 컨테이너 메커니즘으로 등록할 수 있으면서도 모든 처리를 Filter를 구현한 스프링 빈으로 위임해 준다.
Servlet Filter→ DelegatingFilterProxy → SpringSecurityFilterChain
사용자 요청을 위임
💟 FilterChainProxy
스프링 시큐리티는 FilterChainProxy로 서블릿을 지원한다. 이는 스프링 시큐리티가 기본으로 생성하는 특별한 Filter로, SecurityFilterChain을 통해 여러 Filter 인스턴스로 위임할 수 있다. FilterChainProxy는 빈이기 때문에 DelegatingFilterProxy로 감싸져 있다.
DelegatingFilterProxy가 호출하는 스프링 빈의 타입으로서 요청을 위임받고 인가 처리를 한다. 스프링 시큐리티의 Filter를 사용하기 위한 진입점이다. URL 별로 여러 개 등록이 가능하다.
public class FirstFilter implements Filter {
public void init(FilterConfig filterConfig) throws ServletException {
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
//request를 이용해 다음 Filter로 넘어가기 전처리 작업
chain.doFilter(request, response);
//response를 이용해 response에 대한 후처리 작업
}
public void destroy() {
//Filter가 사용한 자원을 반납하는 처리
}
}
💟 SecurityFilterChain
FilterChainProxy가 요청에 사용할 스프링 시큐리티의 Filter들을 선택할 때 사용한다. (= 보안을 위한 작업을 처리하는 필터의 모음으로, 요청 URI path를 기반으로 어떤 Filter와 어떤 Servlet을 매핑할지 결정한다.) 내부에 속한 보안 필터들은 전형적인 빈이지만, DelegatingFilterProxy가 아닌 FilterChainProxy로 등록한다. FilterChainProxy를 직접 서블릿 컨테이너에 등록하거나 DelegatingFilterProxy에 등록하면 스프링 시큐리티가 서블릿을 지원할 수 있는 시작점이 되어 주므로 문제가 생겼을 때 FilterChainProxy부터 디버깅을 해 보면 된다.
💟 Handling Security Exceptions
ExceptionTranslationFilter
는 FilterChainProxy
에 하나의 보안 필터로 추가된다. AccessDeniedException
을 해석하고 AuthenticationException
을 HTTP 응답으로 바꿔 준다.
try {
filterChain.doFilter(request, response); // (1)
} catch (AccessDeniedException | AuthenticationException e) {
if (!authenticated || e instanceof AuthenticationException) {
startAuthentication(); // (2)
} else {
accessDenied(); // (3)
}
}
FilterChain.doFilter(request, response)
를 호출해서 어플리케이션의 나머지 로직을 실행한다.- 인증받지 않은 사용자였거나
AuthenticationException
이 발생했다면 인증을 시작한다.SecurityContextHolder
를 비운다.RequestCache
에HttpServletRequest
를 저장한다. 인증에 성공하면RequestCache
로 요청 처리를 이어간다.AuthenticationEntryPoint
는 클라이언트에credential
을 요청할 때 사용한다.
- 반대로
AccessDeniedException
이었다면 접근을 거부한다. 요청은AccessDeniedHandler
에서 처리한다.
어플리케이션에서 AccessDeniedException
나 AuthenticationException
을 던지지 않으면 ExceptionTranslationFilter
는 아무 일도 하지 않는다.
📚 Reference
'Back-End > Spring' 카테고리의 다른 글
[Spring Security] 권한 부여 구성 요소 (0) | 2022.09.23 |
---|---|
[Spring Security] 구성 요소 및 인증 과정 (0) | 2022.09.22 |
[Spring Boot] JUnit으로 단위 테스트(Unit Test) 코드 작성하기 (0) | 2022.09.18 |
Spring Boot에서 메일 발송하기(Google SMTP) (0) | 2022.09.17 |
[Spring MVC] 애플리케이션 빌드 / 실행 / 배포 (0) | 2022.09.16 |
댓글