ThreadLocal에 저장된다. ThreadLocal은 해당 Thread만 접근 가능한 공간이다. (Thread ID를 key로 하는 Map이라고 생각하자)
기본적으로 다른 Thread의 ThreadLocal에 접근할 수 없기 때문에, 동시성 문제로부터 안전하다. 그러나 ThreadPool을 사용하는 경우 이전의 ThreadLocal에 저장해둔 SecurityContext가 남아있을 수 있으니, Thread가 사용자의 요청을 처리한 뒤 SecurityContext를 삭제할 필요가 있다.
SecurityContext는 현재 사용자의 Authentication 객체를 저장한다.
SecurityContext를 저장한다
SecurityContext(SecurityContextHolder)의 저장전략은 3가지가 있다
| 모드 | 소개 |
|---|---|
| MODE_THREADLOCAL | 기본적인 스레드로컬이다 |
| MODE_INHERITABLETHREADLOCAL | 부모스레드의 스레드 로컬을 자식스레드에서 접근할 수 있다.(ThreadLocal과 InheritableThreadLocal객체를 참조하다.) |
| MODE_GLOBAL | 전역변수로 저장 |
이 저장전략을 지원하기 위해 SecurityContextHolderStrategy인터페이스를 지원한다.
(MODE_PRE_INITIALIZED는 찾아봐야겠다)
/* AuthorizationFilter에서 */ private Authentication getAuthentication() { /* ## SecurityContext 가져오기 ## */ Authentication authentication = this.securityContextHolderStrategy.getContext().getAuthentication(); if (authentication == null) { throw new AuthenticationCredentialsNotFoundException("An Authentication object was not found in the SecurityContext"); } else { return authentication; } }/* SecuirtyContextHolderFilter에서 */ private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { if (request.getAttribute(FILTER_APPLIED) != null) { chain.doFilter(request, response); } else { request.setAttribute(FILTER_APPLIED, Boolean.TRUE); Supplier<SecurityContext> deferredContext = this.securityContextRepository.loadDeferredContext(request);
try {
this.securityContextHolderStrategy.setDeferredContext(deferredContext);
chain.doFilter(request, response);
} finally {
/* ## SecurityContext 삭제 ## */
this.securityContextHolderStrategy.clearContext();
request.removeAttribute(FILTER_APPLIED);
}
}
}
구버전과 다르게 SecurityContext를 가져올때 SecurityContextHolderStrategy를 사용하는 모습을 볼 수 있다.
# SecurityContextRepository
사용자의 인증 정보를 유지하기 위해 사용되는 클래스이다.
사용자의 인증, 권한은 SecurityContext에 저장된다.
SecurityContext는 SecurityContextRepoistory에 의해서 저장된다.
## SecurityContextRepository의 메서드
> [Spring docs - SecurityContext](https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/web/context/SecurityContextRepository.html)
|메서드 | 설명|
|---|---|
|containsContext(HttpServletRequest request)||
|**loadDeferredContext(HttpServletRequest request)**|`loadContext` 대신 이메서드를 사용해야한다|
|saveContext(SecurityContext context, HttpServletRequest request, HttpServletResponse response)||
## SecurityContextRepository의 구현체
|구현객체|설명|
|:---:|:---|
|HttpSessionSecurityContextRepository|session에 저장한다|
|RequestAttributeSecurityContextRepository|ServletRequest에 저장한다. 즉 다음 요청때에는 인증정보가 남지 않는다. (Basic 인증 등)|
|NullSecurityContextRepository |아예 저장하지 않는다.|
|DelegatingSecurityContextRepository | |
# SecurityContextHolderFilter
SecurityContextHolder에 SecurityContext 저장해서 넣어주는 필터이다.
스프링 시큐리티의 필터들 중에서 먼저 실행되는 필터이다. 왜냐하면 사용자의 인증정보를 먼저 불러와야 그 이후의 보안처리가 가능하기 때문이다.
> **인증정보가 없는 경우**
스프링 시큐리티에서는 인증받지 않는 사용자에게 `익명 사용자`라는 토큰을 부여하는데, SecurityContextHolderFilter에서 인증정보가 없으면 SecurityContext에서 `익명사용자` 정보를 넣어준다. 이후 필터들에서 SecurityContext가 null인 것보다 `익명사용자` 토큰이 들어있는 것이 보다 처리하기 유용하기 때문이다.
SecurityContext를 저장하지 않는다. 구 버전의 SecurityContextPersistenceFilter는 알아서 저장까지 해주었지만, 이후에 나올 각 인증 시스템(UsernamePasswordAuthenticationFilter 등)에 의해서 저장할 수 있도록 변경되었다.
SecurityContextHolder는 ThreadLocal에 저장된다. 즉 ThreadPool을 사용한다면, 이전 요청을 보낸 클라이언트의 인증정보가 남아있게 된다. 그러므로 SecurityContextHolderFilter는 SecurityContextHolder에 SecurityContext를 삭제한다. (필터의 실행순서는 `요청 -> A필터 -> 서블릿 -> A필터 -> 응답`이다)