// TheadLocal 변수를 생성
ThreadLocal<Integer> threadLocalValue = new ThreadLocal<>();
// ThreadLocal 변수에 값 쓰기
threadLocalValue.set(1);
// ThradLocal 변수에서 값 읽기
Integer result = threadLocalValue.get();
// ThreadLocal 변수 값 제거
threadLocal.remove();
스프링 mvc는 Thread Per Request 모델을 기반으로 하고 있고 클라이언트 요처을 처리하기 위해 쓰레드풀을 사용하고 있다. 쓰레드 로컬 변수를 사용할떄는 클라이언트 요청처리가 모두 완료된후에 변수를 clear 시켜줘야함
public class SecurityContextHolder {
// ... 생략 ...
private static SecurityContextHolderStrategy strategy;
public static void clearContext() {
strategy.clearContext();
}
public static SecurityContext getContext() {
return strategy.getContext();
}
public static void setContext(SecurityContext context) {
strategy.setContext(context);
}
// ... 생략 ...
}
/**
* SecurityContextHolderStrategy 전략패턴 인터페이스
*/
public interface SecurityContextHolderStrategy {
void clearContext();
SecurityContext getContext();
void setContext(SecurityContext context);
SecurityContext createEmptyContext();
}
/**
* SecurityContextHolderStrategy 인터페이스 ThreadLocal 구현체
*/
final class ThreadLocalSecurityContextHolderStrategy implements SecurityContextHolderStrategy {
private static final ThreadLocal<SecurityContext> contextHolder = new ThreadLocal<>();
@Override
public void clearContext() {
contextHolder.remove();
}
@Override
public SecurityContext getContext() {
SecurityContext ctx = contextHolder.get();
if (ctx == null) {
ctx = createEmptyContext();
contextHolder.set(ctx);
}
return ctx;
}
@Override
public void setContext(SecurityContext context) {
Assert.notNull(context, "Only non-null SecurityContext instances are permitted");
contextHolder.set(context);
}
@Override
public SecurityContext createEmptyContext() {
return new SecurityContextImpl();
}
}
- ThreadLocal을 기본 구현으로 사용한다는 것은 Thread Per Request 모델을 기본 고려했음을 의미함 (물론 Spring Security는 Webflux를 지원하기도 함)
- FilterChainProxy 구현을 보면 finally 블록에서 SecurityContextHolder.clearContext() 메소드를 호출하는 확인할 수 있음
- 이것은 HTTP 요청 처리가 완료되고 Thread가 ThreadPool에 반환되기전 ThreadLocal 변수 값을 제거하기 위함
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
boolean clearContext = request.getAttribute(FILTER_APPLIED) == null;
if (!clearContext) {
doFilterInternal(request, response, chain);
return;
}
try {
request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
doFilterInternal(request, response, chain);
}
catch (RequestRejectedException ex) {
this.requestRejectedHandler.handle((HttpServletRequest) request, (HttpServletResponse) response, ex);
}
finally {
SecurityContextHolder.clearContext();
request.removeAttribute(FILTER_APPLIED);
}
}
- SecurityContext
- SecurityContextHolder 클래스를 통해 코드 어느 부분에서든 SecurityContext에 접근할 수 있음
```java
SecurityContext context = SecurityContextHolder.getContext();
// ... 생략 ...
SecurityContextHolder.setContext(context);
- SecurityContext 자체는 어떤 특별한 기능을 제공하지 않음
- 단순히 org.springframework.security.core.Authentication 객체를 Wrapping 하고 있음
public interface SecurityContext extends Serializable {
Authentication getAuthentication();
void setAuthentication(Authentication authentication);
}
SecurityContextHolder가 쓰레드로컬을 기반으로 하고 있다. -> spring MVF에서 컨트롤러, 레포지토리 , 등에서 SecurityContextHolder를통해 security를 조회할수 있따.
GrantedAuthorities 사용자가 어떤 권한을 가지고 있는지 반환하는 메서드
getPrincipal : 인증이 완료 됐거나 돼지않았어도 그 인증을 포괄적으로 표현 하게 해주
예를들어, 인증전에는 privipal이 리턴하는 타입은 로그인아이디 , 인증후에는 유저 객체
인증전과 인증후에 반환되는 타입이 서로 다르기때문에 Object로 반환형
isAuthenticated : 인증되었는지 아닌지 확인해줌
SecurityContextHolder은 쓰레드로컬 변수를 통해 SecurityContext를 담고 있고SecurityContext를는 Authentication이라는 인터페이스를 담고 있다.
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
if (this.postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
}
String username = obtainUsername(request);
username = (username != null) ? username : "";
username = username.trim();
String password = obtainPassword(request);
password = (password != null) ? password : "";
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
// Allow subclasses to set the "details" property
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
public interface AuthenticationManager {
Authentication authenticate(Authentication authentication) throws AuthenticationException;
}
public interface AuthenticationProvider {
Authentication authenticate(Authentication authentication) throws AuthenticationException;
boolean supports(Class<?> authentication);
}
public class ProviderManager implements AuthenticationManager, MessageSourceAware, InitializingBean {
private List<AuthenticationProvider> providers = Collections.emptyList();
// ... 생략 ...
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
// ... 생략 ...
for (AuthenticationProvider provider : getProviders()) {
if (!provider.supports(toTest)) {
continue;
}
if (logger.isTraceEnabled()) {
logger.trace(LogMessage.format("Authenticating request with %s (%d/%d)",
provider.getClass().getSimpleName(), ++currentPosition, size));
}
try {
result = provider.authenticate(authentication);
if (result != null) {
copyDetails(authentication, result);
break;
}
}
catch (AccountStatusException | InternalAuthenticationServiceException ex) {
prepareException(ex, authentication);
// SEC-546: Avoid polling additional providers if auth failure is due to
// invalid account status
throw ex;
}
catch (AuthenticationException ex) {
lastException = ex;
}
}
// ... 생략 ...
}
// ... 생략 ...
}
**정리
ProcessRequest에서 id,password와 같은 정보를 가지고와서 인증되기전 사용자를 표현하는 Authentication객체를 만든다. Authentication객체는 인증되기전 이기때문에 권한목록도 없고, authenticateflag값도 false를 가지고 있다. principal부분에는 로그인 아이디 의미하는 String타입의 아이디를 가지고 있는다.
이 객체를 AuthenticationManager에 넘겨 AuthenticationManager을 통해 인증처리가 이루어진다. AuthenticationManager 인터페이스의 구현체중 하나인 prividerManager가 실제적으로 인증처리를 담당하는데,prividerManager가 가지고 있는 AuthenticationProvider를 List로 들고 있다.
AuthenticationProvider중 하나가 Authentication을 처리할수 잇는 DaoAuthenticationProvider 를 하나 가지고 있다.
AuthenticationProvider가 인증처리 완료휴 인증이된 사용자를 표현하기 위한 Authentication객체를 만들고 권한을 매핑시켜준후 인증처리 완료한다.