
서블릿 필터에서 서블릿 필터를 구현한 스프링 빈에게 요청을 위임해주는 대리자 역할의 서블릿 필터이다.
스프링 시큐리티는 모든 요청에 대한 인증 및 인가와 같은 보안 처리를 필터 기반으로 처리하고 있는데, 필터에서도 스프링의 기술(스프링 빈)을 사용하고 싶은 요구사항이 있다.
이를 충족하기 위해 스프링에서도 스프링 빈을 만들어 서블릿 필터로 빈을 구현했는데, 이 스프링 빈이 springSecurityFilterChain이다. 하지만, 서블릿 필터에선 스프링 빈들을 주입하거나 사용할 수 없는데, 이러한 필터와 빈을 연결해줄 수 있는 클래스가 DelegatingFilterProxy이다. 이 클래스는 서블릿 필터인데, 요청을 받아서 스프링에게 관리하는 필터에게 요청을 위임하는 역할을 하고 있다.

DelegatingFilterProxy 클래스를 사용해서 스프링 빈에게 요청을 위임하고 그 결과 스프링 빈에서 구현한 서블릿 필터를 이용해 책임을 수행하게 된다.springSecurityFilterChain 이름으로 생성된 빈을 ApplicationContext에서 찾아 요청을 위임한다.
FilterChainProxy는 각 필터들을 순서대로 호출하며 인증/인가처리 및 각종 요청에 대한 처리를 수행한다.springSecurityFilterChain의 이름으로 생성되는 필터 빈DelegatingFilterProxy로 부터 요청을 위임 받고 실제 보안 처리
서블릿 컨테이너에서 스프링 컨테이너로 DelegatingFilterProxy 필터를 이용해 요청을 위임하는 flow는 위 그림과 같다.
DelegatingFilterProxy가 요청을 받게 될 경우 자신이 요청받은 요청 객체를 delegate request로 요청 위임을 한다.springSecurityFilterChain)에서 받게 된다.DelegatingFilterProxy가 필터로 등록될 때 springSecurityFilterChain 동일한 이름으로 등록하여 내부적으로 해당 이름을 가진 객체를 찾는 것.springSecurityFilterChain 필터를 가지고 있는 빈(Bean)이 FilterChainProxy이다.FilterChainProxy에서는 자신이 가진 각각의 필터들을 차례대로 수행하며 보안 처리를 수행한다.스프링 시큐리티에선 보안 설정을 단일 설정이 아닌 여러 개의 설정을 만들어서 동시에 사용할 수 있다.

RequestMatcher 설정http.antMatcher("/admin/**")FilterChainProxy가 각 필터들을 가지고 있음RequestMatcher와 매칭되는 필터가 작동하도록 함
GET 방식으로 /admin 주소로 자원 요청FilterChainProxy 에서 요청을 받아 요청을 처리할 필터를 선택@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/admin/**")
.authorizeRequests()
.anyRequest().authenticated()
.and()
.httpBasic();
}
}
@Configuration
@Order(1)
class SecurityConfig2 extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().permitAll()
.and()
.httpLogin();
}
}
@Order 애노테이션도 순서를 잘 정해서 선언해줘야 한다. 그 이유는 접근 권한 검사 부분에서 좁은 범위부터 설정을 해야 하는 이유와 동일하다. 넓은 범위부터 검사해버리면 통과했을 경우 좁은 범위의 접근 권한을 검사하지 않기 때문에 그 결과 자원 접근을 막지 못하게 된다.
SecurityContext에 저장되어 전역적으로 참조가 가능하다.Authentication authentication = SecurityContextHolder.getContext().getAuthentication()principal : 사용자 아이디 혹은 User 객체를 저장credentials : 사용자 비밀번호authorities : 인증된 사용자의 권한 목록details : 인증 부가 정보Authenticated : 인증 여부(boolean)
UsernamePasswordAuthenticationFilter(인증 필터) 가 요청 정보를 받아 정보 추출 뒤 인증 객체(Authentication) 생성AuthenticationManager 가 인증 객체를 가지고 인증 처리를 수행한다.Authentication 인증 객체를 만들어서 내부의 Principal, Credentials, Authorities, Authenticated 들을 채워 넣는다.SecurityContextHolder 객체 안의 SecurityContext에 저장한다.📌 사용자별
Authentication인증 객체를 어떻게 구분하는가?
위 그림에 나온바와 같이SecurityContextHolder라는 전역 객체 안에SecurityContext에 인증 객체를 저장한다.SecurityContextHolder는 ThreadLocal에 저장되기 때문에 각기 다른 쓰레드 별로 다른SecurityContextHolder인스턴스를 가지고 있어서 사용자 별로 각기 다른 인증 객체를 가질 수 있다.
📌 참고! -
UserDetails
UserDetails를 단순하게 생각하면, Spring Security를 사용하기 위해 필요한 것들과 데이터베이스와의 어댑터라고 생각하면 된다. OAuth2 방식이나 JWT 방식에서 User 엔티티와의 flow에 있어 개인적으론 어디에 왜 사용되는지 이해가 안됐었는데, 이번 포스팅을 하기 위해 구글링을 하던 도중 간단하게 이해해버렸다.
Authentication 객체가 저장되는 보관소로 필요 시 언제든지 Authentication 객체를 꺼내어 쓸 수 있도록 제공되는 클래스ThreadLocal에 저장되어 아무 곳에서나 참조가 가능하도록 설계함HttpSession에 저장되어 애플리케이션 전반에 걸쳐 전역적인 참조가 가능하다.SecurityContext 객체 저장 방식MODE_THREADLOCAL : 스레드당 SecurityContext 객체를 할당. 기본값MODE_INHERITABLETHREADLOCAL : 메인 스레드와 자식 스레드에 관하여 동일한 SecurityContext를 유지MODE_GLOBAL : 응용 프로그램에서 단 하나의 SecurityContext를 저장한다.Authentication authentication = SecurityContextHolder.getContext().getAuthentication()
Authentication) 생성SecurityContextHolder.clearContext() 인증 객체 초기화SecurityContextHolder안의 SecurityContext에 인증 객체 저장ThreadLocal이 SecurityContextHolder를 담고 있는 것이다.SecurityContext에서 최종적으로 HttpSession에 저장 된다.SPRING_SECURITY_CONTEXT라는 이름으로 저장됨.@RestController
public class SecurityController {
@GetMapping("/")
public String index(HttpSession session) {
// authentication, authentication1의 내용은 결과적으로 같다.
Authentication authentication =
SecurityContextHolder.getContext().getAuthentication();
SecurityContext context =
(SecurityContext) session.getAttribute(
HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY);
Authentication authentication1 = context.getAuthentication();
return "home";
}
// 위 요청 매핑과 SecurityContext가 공유되지 않는다.
// 서로 다른 ThreadLocal
@GetMapping("/thread")
public String thread() {
new Thread(
new Runnable() {
@Override
public void run() {
Authentication authentication =
SecurityContextHolder.getContext().getAuthentication();
}
}
).start();
return "thread";
}
}
MODE_INHERITABLETHREADLOCAL전략 사용 시 SecurityContext가 공유된다.
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().permitAll()
.and()
.formLogin();
SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);
}
SecurityContext 객체를 생성하여 SecurityContextHolder에 저장AnonymousAuthenticationFilter에서 AnonymousAuthenticationToken 객체를 SecurityContext에 저장SecurityContext 객체를 생성하여 SecurityContextHolder에 저장UsernamePasswordAuthenticationFilter에서 인증 성공 후 SecurityContext에 UsernamePasswordAuthenticationToken 객체를 저장SecurityContext를 저장SecurityContext를 꺼내어 SecurityContextHolder에 저장SecurityContext 안에 Authentication 객체가 존재하면 계속 인증을 유지한다.SecurityContextHolder.clearContext()
SecurityContextPersistenceFilter는 매번 요청 마다 수행된다.SecurityContextPersistenceFilter내부적으로 HttpSecurityContextRepository가 로직 수행HttpSecurityContextRepository : SecurityContext 객체를 생성, 조회 하는 역할을 하는 클래스SecurityContextHolder)SecurityContext 객체는 null이다. chain.doFilter)AuthFilter)가 인증을 처리한다.Authentication) 생성 후 SecurityContext 객체 안에 저장된다.chain.doFilter)SecurityContext 저장SecurityContextPersistenceFilter가 하는 것이다.SecurityContext 제거 (Clear())SecurityContext가 있는지 확인SecurityContext를 꺼내어 SecurityContextHolder에 넣는다.chain.doFilter)
