기존에는PasswordEncoderFactories의 createDelegatingPasswordEncoder 메서드를 사용하여
DelegatingPasswordEncoder 객체를 생성하여 다시 적절한 PasswordEncoder 객체를 생성했다.
String idForEncode = "bcrypt";
Map encoders = new HashMap<>();
encoders.put(idForEncode, new BCryptPasswordEncoder());
encoders.put("noop", NoOpPasswordEncoder.getInstance());
encoders.put("pbkdf2", new Pbkdf2PasswordEncoder());
encoders.put("scrypt", new SCryptPasswordEncoder());
encoders.put("sha256", new StandardPasswordEncoder());
PasswordEncoder passwordEncoder = new DelegatingPasswordEncoder(idForEncode, encoders);
하지만 위와 같이 DelegatingPasswordEncoder의 생성자로 키값과 맵 객체를 넘겨주면
접근 결정 관리자가 인증 처리 정보를 토대로 사용자가 요청한 리소스에 접근할 충분한 권한이 있는지 확인
권한이 부족하다면 예외 발생, 충분하다면 리소스를 응답
복잡해보이지만 간단하게 말하자면
사용자로부터 인증 정보를 받은 후에 데이터베이스의 인증 정보와 일치하는지 확인 후에
인증에 성공하면 해당 사용자가 리소스에 접근할 권한이 있는지 확인하고
최종적으로 리소스 응답 여부를 결정하는 것이다.
즉, 리소스 요청 ▶ 인증 ▶ 인가 ▶ 리소스 전달 순서다.
필터의 처리 과정
클라이언트 측에서 요청을 하면 아래와 같이 서블릿 필터 단계에서 검증하는 과정을 거친다.
클라이언트의 요청
서블릿 필터 체인에서 DelegatingFilterProxy를 통해 스프링 시큐리티 필터와 연결
FilterChainProxy에서 스프링 시큐리티 필터 체인을 호출
2 - 3번 과정을 반복하며 순서대로 모든 필터 체인들을 통해 검증
검증을 통과했다면 사용자의 요청을 처리하는 과정을 수행
익숙하지 않은 용어들이 많이 나와서 어렵게 보이겠지만 알고나면 간단하다.
우선 서블릿 필터 체인과 스프링 시큐리티 필터 체인은
서로 다른 영역(서블릿 컨테이너와 스프링 컨테이너)에 존재하기 때문에
DelegatingFilterProxy를 통해 두 영역을 이어준다.
두 영역을 이어주는 이유는
서블릿 필터 단계에서 검증을 해야하기 때문이다.
두 영역이 연결되었기에 FilterChainProxy를 통해 서블릿 필터 체인 영역에서
스프링 시큐리티 필터 체인을 호출하여 사용한다.
이 과정들을 반복하여 모든 필터를 거쳐 검증이 끝나고 난 후에야
요청이 처리되기 시작한다.
필터
필터는 일상생활에서 공기청정기의 필터처럼
무언가를 걸러내주는 역할을 담당한다.
Servlet Container
Servlet Filter
DelegatingFilterProxy
FilterChainProxy
Spring Security Filter Chain
DispatcherServlet
Intercepter
Controller, Service, Component 등
서블릿 필터는 위와 같이 실질적인 요청에 대한 처리가 시작되기 전에
검증을 하기 위해 서블릿 컨테이너의 시작점에 존재한다.
필터 체인
체인이라는 말처럼 필터들이 사슬로 연결된 필터의 묶음을 말한다.
서블릿의 필터 체인은 요청 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;
}