[8/2 TIL] SPRING SECURITY(Thread Per Request, ThreadLocal, SecurityContextHolder, 인증처리 흐름, RememberMeAuthenticationFilter)

yumyeonghan·2023년 8월 4일
0
post-custom-banner

🍃프로그래머스 백엔드 데브코스 4기 교육과정을 듣고 정리한 글입니다.🍃

Thread Per Request, ThreadLocal

Thread Per Request 모델

  • WAS는 ThradPool을 생성하고, Tomcat에서 Thread 수는 기본값이 200
  • HTTP 요청이 들어오면 Queue에 적재되고, ThreadPool 내의 특정 Thread가 Queue에서 요청을 가져와 처리하게됨
  • HTTP 요청은 처음부터 끝까지 동일한 Thread에서 처리됨
  • HTTP 요청 처리가 끝나면 Thread는 다시 ThreadPool에 반납됨
  • 즉, WAS의 최대 동시 처리 HTTP 요청의 개수는 ThreadPool의 개수와 같음
  • Thead 갯수를 늘리면 동시 처리 갯수가 늘어나지만, Thread Context 스위칭에 의한 오버헤드도 커지기 때문에 성능이 선형적으로 증가하지는 않음
  • Spring MVC는 Thread Per Request 모델을 사용

ThreadLocal

  • Thread 범위안의 변수이며, 동일 Thread 내에서는 언제든 ThreadLocal 변수에 접근 가능
  • 즉, 동일 Thread내에서 실행되는 Controller, Service, Repository, 도메인 모델 어디에서든 명시적인 파라미터 전달 필요없이 ThreadLocal 변수에 접근할 수 있음
  • ThreadPool과 함께 사용하는 경우 Thread가 ThreadPool에 반환되기 직전 ThreadLocal 변수 값을 반드시 제거해야함
    • 다른 요청을 처리하기 위해 ThreadPool에서 Thread를 하나 가져왔는데 이전 요청 처리에 사용된 ThreadLocal 변수가 남아있고, 이를 참조하여 잘못된 동작을 수행할 수 있음

SecurityContextHolder

그림 참조

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);
	}
  	// ... 생략 ...
}
  • SecurityContext 데이터를 쓰거나 읽을 수 있는 API 제공
  • 기본적으로 ThreadLocalSecurityContextHolderStrategy을 구현체로 갖는 strategy 필드를 갖고, 이를 사용해서 로직 수행
    • ThreadLocal을 사용한다는 것은 Thread Per Request 모델을 고려했음을 의미
    • 또한 위에서 언급한 Thread Per Request 모델의 주의점을 해결
      • FilterChainProxy의 doFilter() 메서드에서 finally 부분에 SecurityContextHolder.clearContext() 메소드를 활용
  • 따라서 strategy는 ThreadLocal이므로 Spring MVC환경에서 Controller, Service, Repository 어느 부분에서든지 SecurityContext 조회 가능

코드 어느 부분에서든지 SecurityContext에 접근할 수 있다는데 SecurityContext이 뭘까?

SecurityContext

public interface SecurityContext extends Serializable {

	Authentication getAuthentication();

	void setAuthentication(Authentication authentication);
}
  • 단순히 Authentication 객체를 조회하거나 설정밖에 안함

Authentication

  • 사용자를 표현하는 인증 토큰 인터페이스며, 인증 주체를 표현하는 Principal과 사용자의 권한을 의미하는 GrantedAuthority 목록을 포함
    • AnonymousAuthenticationToken 클래스는 익명 사용자를 표현하기 위한 Authentication 인터페이스 구현체
    • UsernamePasswordAuthenticationToken 클래스는 로그인 아이디/비밀번호 기반 Authentication 인터페이스 구현체
    • RememberMeAuthenticationToken 클래스는 remember-me 기반 Authentication 인터페이스 구현체
  • 인증이 완료되거나 혹은 인증되지 않은 사용자 모두를 포괄적으로 표현하며, 인증 여부를 확인할 수 있음
    • 사용자의 인증 완료 여부에 따라 Principal 값이 달라짐
      • 로그인 전 Principal: 로그인 아이디 (String)
      • 로그인 후 Principal: org.springframework.security.core.userdetails.User 객체

인증(Authentication)처리 흐름

AbstractAuthenticationProcessingFilter

	//구현체인 UsernamePasswordAuthenticationFilter 클래스의 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.trim() : "";
		String password = obtainPassword(request);
		password = (password != null) ? password : "";
		UsernamePasswordAuthenticationToken authRequest = UsernamePasswordAuthenticationToken.unauthenticated(username,
				password);
		// Allow subclasses to set the "details" property
		setDetails(request, authRequest);
		return this.getAuthenticationManager().authenticate(authRequest);
	}
  • 사용자 인증을 처리하기 위한 필터로 가장 대표적인 것으로 UsernamePasswordAuthenticationFilter 구현체가 있음
  • 사용자 인증을 위한 정보를 취합하고, Authentication 객체를 생성함
    • UsernamePasswordAuthenticationFilter 구현에서는 로그인 아이디/비밀번호를 취합하고, Authentication 인터페이스 구현체중 하나인UsernamePasswordAuthenticationToken 객체를 생성함
  • 아직, 인증이 완료되지 않은 Authentication 객체가 AuthenticationManager 객체로 전달됨
  • AuthenticationManager에 의해 인증이 정상적으로 완료된다면 새롭게 만들어진 Authentication 객체를 반환함
    • 여기서 새롭게 만들어진 Authentication 객체는 인증이 완료된 상태이고, GrantedAuthority 권한 목록을 포함하고 있음

AuthenticationManager

public interface AuthenticationManager {

	Authentication authenticate(Authentication authentication) throws AuthenticationException;
    
}
  • AuthenticationManager 인터페이스는 사용자 인증을 위한 API를 제공함
  • 기본 구현체로 org.springframework.security.authentication.ProviderManager 클래스가 있음

ProviderManager

//실제 사용자 인증을 처리할 인터페이스
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 {
    	Authentication result = null;
		// ... 생략 ...
		for (AuthenticationProvider provider : getProviders()) {
			if (!provider.supports(toTest)) {
				continue;
			}
			// ... 생략 ...
			try {
				result = provider.authenticate(authentication); //반환
				if (result != null) {
					copyDetails(authentication, result);
					break;
				}
			}
			// ... 생략 ...
		}
		// ... 생략 ...
	}
	// ... 생략 ...
}
  • AuthenticationProvider 인터페이스 구현체가 실제 사용자 인증을 처리하게 됨
  • AuthenticationProvider를 List로 갖고 있음
  • List중 어떤 AuthenticationProvider가 실제 인증을 처리할지 결정할 수 있음
    • 주어진 Authentication 객체에 대해 supports() 메소드가 true 를 반환하는 AuthenticationProvider 객체가 인증을 처리함
    • 예를 들어 UsernamePasswordAuthenticationToken 타입의 인증 요청은 DaoAuthenticationProvider가 처리함
  • 결국 실질적으로 인증 처리를 하는것은 ProviderManager가 아니라 AuthenticationProvider임

RememberMeAuthenticationFilter

  • 인증되지 않은 사용자의 HTTP 요청이 remember-me(쿠키 파라미터명) 쿠키(Cookie)를 갖고 있다면, 사용자를 자동으로 인증처리함
  • 실제 사용자 인증은 RememberMeServices 인터페이스 구현체를 통해 처리됨
    • TokenBasedRememberMeServices: MD5 해시 알고리즘 기반 쿠키 검증
    • PersistentTokenBasedRememberMeServices: 외부 데이터베이스에서 인증에 필요한 데이터를 가져오고 검증함
      • 사용자마다 고유의 Series 식별자가 생성되고, 인증 시 마다 매번 갱신되는 임의의 토큰 값을 사용하여 보다 높은 보안성을 제공함
    • remember-me 기능을 활성화하고 로그인을 하면, 사용자의 정보를 갖고 쿠키를 생성하는 역할도 함
  • RememberMeAuthenticationToken
    • remember-me 기반 Authentication 인터페이스 구현체
    • RememberMeAuthenticationToken 객체는 언제나 인증이 완료된 상태만 존재함
  • RememberMeAuthenticationProvider
    • RememberMeAuthenticationToken 기반 인증 처리를 위한 AuthenticationProvider
    • 앞서 remember-me 설정 시 입력한 key 값을 검증함
    • 그외 과정은 일반적인 로그인 처리 과정과 동일

명시적인 로그인 아이디/비밀번호 기반 인증 사용와 권한 구분

  • remember-me 기반 인증 결과: RememberMeAuthenticationToken
  • 로그인 아이디/비밀번호 기반 인증 결과: UsernamePasswordAuthenticationToken
  • remember-me 기반 인증은 로그인 기반 인증 보다 보안상 약한 인증이기 때문에 isFullyAuthenticated() 사용
profile
웹 개발에 관심 있습니다.
post-custom-banner

0개의 댓글