Web/Spring

[Spring] Spring Security 기본 이해 - (3)

HYJJ 2022. 6. 27. 11:03

Spring Security Section 1, Section 2


목적 :  ExceptionTranslationFilter, RequestCacheAwareFilter, CsrfFilter, DelegatingProxyChain, FilterChainProxy, SecurityContextHolder, SecurityContext 이해


- SecurityContextHolder, SecurityContext

SpringSecurityContextHolder는 누가 authenticated 되었는지 상세 정보를 저장하는 곳이다. Spring Security는 어디서 SpringSecurityHolder가 생성되었는지 신경쓰지 않는다. 

 

SpringContext는 SecurityContextHolder에서 얻을 수 있는 것으로 Authentication 객체를 포함한다. 

 

이에 대한 코드는 아래와 같다.

SecurityContext context = SecurityContextHolder.createEmptyContext();
Authentication authentication =
    new TestingAuthenticationToken("username", "password", "ROLE_USER");
context.setAuthentication(authentication);

SecurityContextHolder.setContext(context);
  1.  빈 SpringContext를 만드는 것에서 시작한다. 멀티 스레드 환경에서 race condition (여러 프로세스들이 공유 데이터에 접근하는 상황) 을 피하기 위해 new로 새로운 인스턴스를 생성하는 대신 set을 통해 새로운 SpringContext를 생성한다.
  2. 이렇게 Authentication 객체가 생성됐으면 TestingAuthenticationToken을 사용한다. (보통 가장 흔하게 UsernamePasswordAuthenticationToken을 사용함.)
  3. 최종적으로 SpringContext를 SecurityContextHolder에 저장한다. Spring에서는 이 정보를 Authorization 하는데에 사용한다.

SpringContextHolder에 담긴 정보를 얻기 위해서는 아래의 코드를 통해 확인할 수 있다.

SecurityContext context = SecurityContextHolder.getContext();
Authentication authentication = context.getAuthentication();
String username = authentication.getName();
Object principal = authentication.getPrincipal();
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();

기본적으로 SecurityContextHolder는 이러한 정보를 사용하는 데에 ThreadLocal을 사용한다. 이 의미는 SecurityContext는 동일한 스레드에서만 적용 가능하다는 뜻이다. ThreadLocal을 사용하는 것이 principal 요청 후(애플리 케이션에 접근하는 사용자가 누구인지 확인)에 스레드를 비울 때 훨씬 더 안전하다. 

 

 

- ExceptionTranslationFilter

ExceptionTranslationFilter는 AccessDeniedException과 AuthenticationException을 HTTP response로 바꾸는 역할을 한다. 위 그림에서 볼 수 있는 것과 같이 FilterProxyChain에 ExceptionTranslationFilter를 주입하여 사용한다. 

 

과정은 다음과 같다.

 

1. FilterChain.doFilter(request, response)를 통해 ExceptionTranslationFilter가 발생한다.

2. 만약 사용자가 인증되지 않았거나 AuthenticationException을 발생시킨다면 Authentication(인증)이 발생한다. Authentication이 시작하는 단계는 아래와 같다.

  • SecurityContextHolder 즉 Authentication을 저장하는 부분을 지운다.
  • HttpServletRequest는 RequestCache에 저장된다. 사용자가 Authentication에 성공한다면 RequestCache는 기존 request를 재사용 하는 데에 쓰인다.
  • AuthenticationEntryPoint는 클라이언트로부터 자격(credentials)을 요청할 때 사용한다. 
  • 그렇지 않다면(AccessDeniedException 이 발생했다면) 접근은 제한된다. AccessDeniedHandler가 접근을 거부하기 위해 발생한다. 

ExceptionTranslationFilter에 pseudo 코드는 아래와 같다. 

try {
	filterChain.doFilter(request, response);
} catch (AccessDeniedException | AuthenticationException ex) {
	if (!authenticated || ex instanceof AuthenticationException) {
		startAuthentication();
	} else {
		accessDenied();
	}
}

 

1. doFilter 메소드를 통해 FilterChain을 가져온다. 만약 애플리케이션 다른 부분에서 Exception(AuthenticationException이거나 AccessDeniedException)을 발생시켰다면 에러 핸들링을 하는 부분이다.

2. 만약 사용자가 authenticated 하지 않거나 AuthenticationException을 발생시켰다면 authentication을 시작한다.

3. 그렇지 않았다면 access는 거부된다.   

 

 

 

 

 

 

참고 자료

https://stackoverflow.com/questions/49575487/using-spring-security-with-thread-pools-leads-to-a-race-condition-when-threads-a

https://docs.spring.io/spring-security/reference/servlet/architecture.html#servlet-security-filters

https://docs.spring.io/spring-security/site/docs/4.0.x/apidocs/org/springframework/security/web/savedrequest/RequestCacheAwareFilter.html