사용자의 요청이 왔을 때 스프링 시큐리티가 어떤 일들을 수행하는지
로그인 과정을 통해 알아보겠다.
인증과 인가
우선은 인증과 인가의 개념과 차이에 대해서 알아야 한다.
이름은 비슷하지만 둘의 역할은 조금 다른데
인증은 말그대로 만약 사용자라는 객체가 있다면
해당 객체가 유효한지 인증해주는 것이다.
인가는 인증된 객체가 어디까지 접근할 수 있는지
객체의 권한을 확인한다.
로그인을 통해 사이트의 인증을 받았다면
로그인한 사용자의 권한에 따라서
이용할 수 있는 기능을 제한하는 것이 인가라고 볼 수 있다.
사용자 시점
사용자가 정확한 인증 정보를 서버에 제공 하였다면
아래와 같은 순서대로 요청이 처리된다.
- 정확한 로그인 정보(Credential) 제공
- 로그인 성공
- 접근 권한이 있는 요청 전달
- 응답 받은 정보 조회
스프링 시큐리티 시점
- 사용자가 서버에 리소스를 요청
- 스프링 시큐리티 인증 관리자가 Credential 요청
- 사용자가 Credential 제공
- 인증 관리자가 데이터베이스에서 사용자 인증 정보 조회
- 유효한 Credential인지 확인
- 유효하지 않다면 예외 발생, 유효하다면 인증 처리
- 접근 결정 관리자가 인증 처리 정보를 토대로 사용자가 요청한 리소스에 접근할 충분한 권한이 있는지 확인
- 권한이 부족하다면 예외 발생, 충분하다면 리소스를 응답
복잡해보이지만 간단하게 말하자면
사용자로부터 인증 정보를 받은 후에 데이터베이스의 인증 정보와 일치하는지 확인 후에
인증에 성공하면 해당 사용자가 리소스에 접근할 권한이 있는지 확인하고
최종적으로 리소스 응답 여부를 결정하는 것이다.
즉, 리소스 요청 ▶ 인증 ▶ 인가 ▶ 리소스 전달 순서다.
필터의 처리 과정
클라이언트 측에서 요청을 하면 아래와 같이 서블릿 필터 단계에서 검증하는 과정을 거친다.
- 클라이언트의 요청
- 서블릿 필터 체인에서 DelegatingFilterProxy를 통해 스프링 시큐리티 필터와 연결
- FilterChainProxy에서 스프링 시큐리티 필터 체인을 호출
- 2 - 3번 과정을 반복하며 순서대로 모든 필터 체인들을 통해 검증
- 검증을 통과했다면 사용자의 요청을 처리하는 과정을 수행
익숙하지 않은 용어들이 많이 나와서 어렵게 보이겠지만 알고나면 간단하다.
우선 서블릿 필터 체인과 스프링 시큐리티 필터 체인은
서로 다른 영역(서블릿 컨테이너와 스프링 컨테이너)에 존재하기 때문에
DelegatingFilterProxy를 통해 두 영역을 이어준다.
두 영역을 이어주는 이유는
서블릿 필터 단계에서 검증을 해야하기 때문이다.
두 영역이 연결되었기에 FilterChainProxy를 통해 서블릿 필터 체인 영역에서
스프링 시큐리티 필터 체인을 호출하여 사용한다.
이 과정들을 반복하여 모든 필터를 거쳐 검증이 끝나고 난 후에야
요청이 처리되기 시작한다.
필터
필터는 일상생활에서 공기청정기의 필터처럼
무언가를 걸러내주는 역할을 담당한다.
- Servlet Container
- Servlet Filter
- DelegatingFilterProxy
- FilterChainProxy
- Spring Security Filter Chain
- DispatcherServlet
- Intercepter
- Controller, Service, Component 등
- Servlet Filter
서블릿 필터는 위와 같이 실질적인 요청에 대한 처리가 시작되기 전에
검증을 하기 위해 서블릿 컨테이너의 시작점에 존재한다.
필터 체인
- 체인이라는 말처럼 필터들이 사슬로 연결된 필터의 묶음을 말한다.
- 서블릿의 필터 체인은 요청 URI를 기반으로 어떤 필터와 서블릿을 매핑할지 결정한다.
- 필터 체인 안에서 필터의 순서를 지정할 수 있다.
- @Order 어노테이션이나 Ordered 인터페이스를 구현해 순서를 지정할 수 있다.
- FilterRegistrationBean을 이용해 필터의 순서를 명시적으로 지정할 수 있다.
커스텀 필터 만들기
public class MyCustomFilter implements Filter {
// 필터 초기화
@Override
public void init(FilterConfig filterConfig) throws ServletException {
Filter.super.init(filterConfig);
System.out.println("필터 생성");
}
@Override
public void doFilter(ServletRequest request,
ServletResponse response,
FilterChain chain)
throws IOException, ServletException {
// 다음 Filter 전의 전처리 작업
System.out.println("필터 시작");
chain.doFilter(request, response); // 필터가 처리하는 실제 로직
// 후처리 작업
System.out.println("필터 종료");
}
// 필터가 사용한 자원 반납
public void destroy() {
System.out.println("필터 삭제");
Filter.super.destroy();
}
}
우선 Filter 인터페이스를 구현할 MyCustomFilter 클래스를 생성한다.
init 메서드를 통해 필터를 초기화 한 후에
doFilter 메서드를 통해 전처리 및 후처리, 실질적인 필터의 처리 로직을 구현한다.
이때 전처리는 ServletRequest 객체를 사용하고
후처리는 ServletResponse 객체를 사용한다.
destroy 메서드를 사용하여 사용한 자원을 반납하여 필터를 끝낸다.
@Configuration
public class FilterConfiguration {
@Bean
public FilterRegistrationBean<MyCustomFilter> customFilterRegister() {
FilterRegistrationBean<MyCustomFilter> registrationBean = new FilterRegistrationBean<>(new MyCustomFilter());
return registrationBean;
}
}
커스텀 필터를 적용하기 위해 FilterConfiguration 클래스를 만든 후에
FilterRegistrationBean의 생성자로 필터 인터페이스를 구현한 MyCustomFilter를 넘겨주어
빈으로 등록하여 서블릿 필터 체인에서 사용할 수 있도록 한다.
@Bean
public FilterRegistrationBean<MyCustomFilter> customFilterARegister() {
FilterRegistrationBean<MyCustomFilterA> registrationBean = new FilterRegistrationBean<>(new MyCustomFilterA());
registrationBean.setOrder(1);
return registrationBean;
}
@Bean
public FilterRegistrationBean<MyCustomFilter> customFilterBRegister() {
FilterRegistrationBean<MyCustomFilterB> registrationBean = new FilterRegistrationBean<>(new MyCustomFilterB());
registrationBean.setOrder(2);
return registrationBean;
}
만약 여러 개의 필터를 사용하는 경우에는 위와 같이 우선순위를 지정할 수도 있는데
지정한 숫자가 낮을 수록 우선순위가 높은 필터다.
'Back-End > Security' 카테고리의 다른 글
[스프링 시큐리티] 인증 및 인가 처리 흐름 (0) | 2023.07.11 |
---|---|
[스프링 시큐리티] DelegatingPasswordEncoder (0) | 2023.07.11 |
[스프링 시큐리티] 커스텀 로그인 인증 구현 (0) | 2023.07.11 |
[스프링 시큐리티] 커스텀 인증 처리 구현 (0) | 2023.07.11 |
[스프링 시큐리티] 회원가입 구현 (0) | 2023.07.10 |