서블릿 필터에서 서블릿 필터를 구현한 스프링 빈에게 요청을 위임해주는 대리자 역할의 서블릿 필터이다.
스프링 시큐리티는 모든 요청에 대한 인증 및 인가와 같은 보안 처리를 필터 기반으로 처리하고 있는데, 필터에서도 스프링의 기술(스프링 빈)을 사용하고 싶은 요구사항이 있다.
이를 충족하기 위해 스프링에서도 스프링 빈을 만들어 서블릿 필터로 빈을 구현했는데, 이 스프링 빈이 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
)