안녕하세요.! 개발이 즐거운 코세입니다.!
이번 포스팅은 SecurityFilterChain에 대해 탐구해본 내용을 정리하여 작성하겠습니다.!
SecurityFilterChain을 이해하는 데 도움이 될 수 있는 두 가지 사진을 먼저 첨부했습니다.
client가 request 요청을 보내면, Spring Container 이전에 Servlet Container에서 먼저 Filter에 의해 여러 가지 요청이 처리됩니다. SpringContainer와 Was 서버 간에 request 요청이 연결되어야 하는데, 이를 수행하는 Filter가 DelegatingFilterProxy입니다.
DelegatingFilterProxy는 SpringContainer에 존재하는 FilterChainProxy에게 해당 요청을 위임함으로써, request를 Spring Container에서 처리할 수 있습니다. 위임 전략을 사용하는 이유는, 스프링에서 제공하는 다양한 기술들을 사용할 수 있기에 다양한 장점이 있다고 합니다. 이 부분은 추후 포스팅에서 깊게 다루도록 하겠습니다.
FilterChainrProxy로 위임 받은 필터는 SecurityFilterChain이라는 구현체를 가지고 있고 그 안에 다양한 SecurityFilter가 존재합니다. 사진에 보이는 작은 글씨의 0, n은 SecurityFilter가 다수의 필터를 가질 수 있음을 의미하고, 배열에 관리되기 때문에 각 인덱스에는 필터의 중요도(처리 우선순위)라고 할 수 있습니다.
SecurityFilter_0 ~ SecurityFilter_n 까지 반복문이 돌면서, 해당 request에 매핑되는 antMatcher를 찾는다면, 필터는 해당 filter의 메소드에 작성된 체인들을 설정 요구사항을 확인하며 처리합니다. 최종적으로 filter를 통과하면, DispatcherSerlvet으로 요청이 위임하여 성공 핸들러(사용자 정의 핸들러 혹은 controller)가 실행됩니다.
다수의 SecurityFilterChain의 동작 원리를 파악하기 위해, SecurityConfig, SecurityConfig2 작성하였습니다.
@Slf4j
@Configuration
@EnableWebSecurity
public class SecurityConfig2 {
@Order(0)
@Bean
protected SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.antMatcher("/users/**")
.authorizeRequests()
.anyRequest().hasRole("ADMIN");
return http.build();
}
}
@Slf4j
@RequiredArgsConstructor
@Configuration
public class SecurityConfig {
@Bean
public UserDetailsService userDetailsService(PasswordEncoder encoder) {
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(User.withUsername("admin").password(encoder.encode("1111")).roles("ADMIN", "USER", "SYS").build());
manager.createUser(User.withUsername("user").password(encoder.encode("1111")).roles("USER").build());
manager.createUser(User.withUsername("sys").password(encoder.encode("1111")).roles("SYS", "USER").build());
return manager;
}
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Order(1)
@Bean
protected SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().permitAll()
.and()
.formLogin();
return http.build();
}
}
먼저, 간단한 코드 설명을 하면, @Configuration를 선언함으로써 해당 클래스가 Spring container에 관리되는 config 설정임을 알려주며, @Bean을 등록할 수 있도록 합니다.
UserDetailsService는 Spring Security가 실행될 때, 유저 정보에 대한 로직을 작성할 수 있는 Service로 이에 대한 부분도 추후 포스팅에서 다루도록 하겠습니다. 이 부분은 다양한 Role이 있는 아이디와 패스워드로 계정을 생성하기 위해 작성한 Bean입니다.
@Order는 해당 SecurityFilterChain의 우선순위를 나타내는 어노테이션입니다. 앞에서 말씀드렸듯이 SecurityFilterChain은 우선순위에 따라 해당 필터가 작동합니다. 따라서, admin만 접근 가능한 url일 때, 다른 필터보다 먼저 처리되어야 하는 경우 해당 우선순위를 (0 순서로 가장 우선순위) 작성하면 됩니다.
SecurityConfig2는 antMacher("/users/**)로 설정하였고, 해당 request가 오면, "ADMIN"만이 접근 가능하도록 설정하였습니다.
SecurityConig는 모든 사용자가 접근 가능하되, formLogin 절차를 거쳐야 함을 확인할 수 있습니다.
사진 크기가 달라서 이쁘지 않지만, url도 함께 보여드려야하기 때문에 로그인화면과 해당 ROLE에 따른 접근 결과를 함께 첨부하였습니다.
먼저, USER 권한이 있는 user는 로그인 후, /users에 접근하면, 권한이 없는 사용자가 접근했기 때문에 403 에러를 발생시킵니다.
반면, ADMIN 권한이 있는 admin은 로그인 후, /admin에 접근하면, 권한이 있는 사용자가 접근했기 때문에 @RestController에서 정의한 "/users"로 매핑되어 users가 출력됩니다.
IntelliJ는 정말 많은 기능을 제공해줍니다. 사실 저는 과거에 머신러닝 개발 공부를 했었어서, 중단점 사용을 많이 하지 않았는데, 백엔드 분야와 사랑에 빠진 후에...? 중단점을 사용하게 되었습니다.
간단한 방법은 인텔리제이의 라인수와 코드 사이에 왼쪽 클릭하면 빨간색 점이 나오는데, 이 부분이 중단점입니다. 디버깅(초록색 벌레)를 누르면, 해당 WAS가 실행될 때, 중단점에서 멈추게 됩니다. 이때 F9를 누르면 중단점을 넘어가면서 해당 중단점이 실행되었을 때 결과를 콘솔로 확인하실 수 있습니다.
기본 localhost:8080에 접속하면, 밑에 보이는 filterChains에 2개의 사이즈가 있음을 보실 수 있습니다. 이는 제가 위에서 정의한 두 개의 SecurityFilter 임을 의미하고, "/users" 접근 이외에는 접근이 가능하도록 설정했으므로 해당 결과를 보실 수 있습니다.
admin으로 로그인 후 /users로 url을 작성하면, 중단점이 실행되기 전까지 was가 멈춘상태로 존재하는데, 필터를 상세히 보면, 0, 1의 두개의 필터가 존재함을 확인할 수 있습니다.
"/users"로 url을 요청했으므로 0번째 인덱스에 존재하는 필터와 매핑되어 chain.mathes가 작동하면서 chain.getFilters를 호출합니다.
최종적으로 ROLE의 판단이 끝난 후, 정상처리 되어 결과가 나온 것을 보실 수 있습니다.
이번 포스팅은 정말 길었습니다. 정수원님의 인프런 강의를 통해 Spring Security를 배우고 있는데, Spring Security에 대해 깊게 이해할 수 있어서 정말 즐겁습니다.! 저는 최근 버전을 사용하고 있기 때문에 강의를 참고하면서 SecurityFilterChain를 이용했습니다.
잘못된 부분이 있다면, 댓글 부탁드립니다. 피드백은 언제나 환영합니다.
항상 즐겁게 배우겠습니다.!!!
오늘도 좋은 하루되십시요.! 감사합니다.!
참고자료: 인프런 정수원님 강의 - 스프링 시큐리티 - Spring Boot 기반으로 개발하는 Spring Security
작성자님의 글 덕분에 궁금증을 해결할 수 있었습니다. 감사합니다. 더 많은 정보를 확인하고 싶어서 혹시 정보의 출처를 알 수 있을까요?