1. 동작 원리
Spring Security 의존성이 없는 경우 'HTTP 요청 → WAS → 필터 → 서블릿 → 컨트롤러' 순으로 작동한다. 그림으로 표현하면 다음과 같다.
반면에, Spring Security 의존성을 추가하면 필터를 하나 추가하여 중간에 요청을 가로챈다. 가로채진 요청은 Spring Security 로직을 마친 후 다음 필터로 복귀한다.
- DelegatingFilterProxy: Bean Filter를 찾아 해당 필터로 요청을 넘긴다. (FilterChainProxy가 없다면 Bean Filter들을 DelegatingFilterProxy에 하나하나 등록해야 한다.)
- FilterChainProxy: Spring Security가 제공하는 특별한 Bean Filter다. 우리는 이 FilterChainProxy 덕분에 SecurityFilterChain을 통해 많은 Filter instance들로 요청을 전달할 수 있다. (FilterChainProxy에 담긴 여러 필터들을 DelegatingFilterProxy에 등록해준다.)
- SecurityFilterChain: 필터들의 묶음으로 생각하면 된다. SecurityFilterCahin들은 FilterChainProxy가 현재 요청에 적용해야할 필터를 결정할 때 사용된다. (SecurityFilterChain에 있는 Filter들은 DelegatingFilterProxy에 바로 등록되지 않고 먼저 FilterChainProxy에 등록된다. 이 등록된 SecurityFilterChain들을 FilterChainProxy가 DelegatingFilterProxy에 등록해주는 것 같다.)
SecurityFilterChain은 아래와 같이 여러 개일 수 있다.
2. SecurityFilterChain 등록
SecurityFilterChain을 등록하기 위해서는 아래와 같이 SecurityFilterChain을 리턴하는 @Bean 메소드를 등록하면 된다. (여러 개도 가능하다.)
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain1(HttpSecurity http) throws Exception{
return http.build();
}
@Bean
public SecurityFilterChain filterChain2(HttpSecurity http) throws Exception {
return http.build();
}
}
FilterChainProxy는 여러 개의 SecurityFilterChain 중 하나를 선택하여 요청을 전달한다. 이때 기준은 아래와 같다.
- 필터 체인에 대한 RequestMatcher 값 일치 여부
- 등록 인덱스 순 (위에서 filterChain1이 filterChain2보다 먼저 등록되었기 때문에 filterChain1의 인덱스가 0이고 filterChain2의 인덱스가 1이다. 인덱스를 정하고 싶다면 @Order 어노테이션을 사용하면 된다.)
RequestMatcher 값은 아래와 같이 설정할 수 있다.
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.securityMatchers((auth) -> auth.requestMatchers("/user"))
.authorizeHttpRequests((auth) -> auth
.requestMatchers("/user").permitAll());
return http.build();
}
이때 주의할 점은 securityMatchers()를 통해 설정하는 것이지, authorizeHttpRequest를 통한 인가 설정과는 다르다는 점을 주의해야 한다.
- 추가적으로 @EnableWebSecurity(debug=true)를 사용하면 요청이 오는 경우 securityFilterChain의 필터 목록을 출력할 수 있다.
3. 필터 적용 제외 방법
굳이 필터를 적용하지 않아도 되는 요청의 경우 아래 코드를 활용해 제외할 수 있다. 이 경우 하나의 SecurityFilterChain이 생성되며 해당 필터 체인은 0번 인덱스로 설정된다.
@Bean
public WebSecurityCustomizer webSecurityCustomizer() {
return web -> web.ignoring().requestMatchers("/ignore");
}
4. 인증 아키텍처
4.1. 용어 정리
인증 과정을 알아보기 전에 Spring Security에서 사용하는 용어들을 알아보자.
- Authentication(인증): 사용자가 본인인지 확인하는 절차
- Authorization(인가): 인증된 사용자가 요청하는 자원을 사용할 권한이 있는지 확인하는 절차
- Principal(접근 주체): 보호받는 자원에 접근하는 대상
- Credential(비밀번호): 접근 주체의 비밀번호
Spring Security는 기본적으로 인증 후 인가를 진행하여, 인가 과정에서 리소스 접근 권한이 있는지 확인한다. 이러한 인증과 인가를 위해서 Spring Security는 Principal을 아이디, Credential을 비밀번호로 사용하는 Credential 기반의 인증 방식을 사용한다. 주요 모듈은 다음과 같다.
- SecurityContextHolder: Spring Security가 인증된 사용자의 정보를 저장하는 공간이다.
- SecurityContext: 인증된 사용자의 Authentication을 보관한다.
- Authentication: 접근 주체의 인증 정보와 권한을 가진다.
4.2. 인증 과정
- 사용자가 로그인 정보가 담긴 Http 요청을 보낸다.
- AuthenticationFilter가 요청을 받으면 요청에 담긴 username과 password를 통해 UsernamePasswordAuthenticationToken(=Authentication object)을 만든다.
- 만들어진 AuthenticationToken을 AuthenticationManager에 전달한다. AuthenticationManager는 인터페이스고, 실제 구현체는 ProviderManager다. ProviderManager는 (유저 요청들을 인증하는데 사용되는) AuthenticationProvider들을 리스트형태로 가지고 있다.
- AuthenticationManager가 AuthenticationProvider(들)에게 인증을 요청한다.
- 몇 가지 AuthenticationProvider는 Username을 기반으로 UserDetails를 얻기 위해 UserDetailService를 사용한다. (UserDetailsService는 username을 통해 DB에서 사용자 정보를 가져온다.)
- 넘겨받은 사용자 정보로 UserDetails를 만든다. (UserDetails는 인터페이스고, 실제 구현체는 User인 것이다.)
- AuthenticationProvider(들)는 UserDetails를 받고 사용자 정보와 비교한다.
- 만약 인증에 성공했다면, 사용자 정보가 담긴 Authentication 객체를 반환한다. 실패한 경우 AuthenticationException을 던진다.
- AuthenticationManager는 받은 Authentication 객체를 Authentication Filter에 전달한다.
- AuthenticationFilter는 미래에 Authentication 객체를 SecurityContext에 저장한다.
출처:
https://docs.spring.io/spring-security/reference/
https://chathurangat.wordpress.com/2017/08/23/spring-security-authentication-architecture/#more-54
'BackEnd > Spring Security' 카테고리의 다른 글
[Spring Security] 세션 로그인 (간단한 기본 동작 설명 및 실습) (0) | 2024.08.13 |
---|