Spring Security Internals -1

김재현·2022년 8월 20일
0

Programmers

목록 보기
20/28

Spring Security Essentials

Thread Per Request 모델과 ThreadLocal

Thread Per Request 모델 개요

  • Thread Per Request : Tomcat 같은 servlet 컨테이너 위에서 전통적으로 사용되던 병렬처리 기법.
    Spring WEB MVC도 서블릿 컨테이너 위에서 동작하기 때문에 Thread Per Request을 사용한다.
    • WAS는 ThradPool을 생성함 (Tomcat 기본값 200)
    • HTTP(클라이언트) 요청이 들어오면 Queue에 적재되고, ThreadPool에서 대기중이던 특정 Thread가 Queue에서 요청을 가져와 처리하게됨
    • HTTP 요청은 처음부터 끝까지 동일한 Thread에서 처리됨
    • HTTP 요청 처리가 끝나면 Thread는 다시 ThreadPool에 반납됨
    • 즉, WAS의 최대 동시 처리 HTTP 요청의 갯수는 ThreadPool의 갯수와 같음
    • ThreadPool의 Thead 개수를 엄청 늘리면 성능이 좋아지지 않을까? > 일반적으로 동시 처리 갯수가 늘어나서 맞는 말이지만, 임계치를 넘기는 순간 오히려 성능이 하락할수도 있다.
      스레드 그 자체도 서버의 메모리, cpu연산을 차지하는 리소스이기 때문에 스레드 개수가 많아질수록 스레드를 관리하는 오버헤드도 커지게 되기에, 성능이 반드시 선형적으로 증가하지는 않는다.

  • 최근 소개된 WebFlux 같은 기술은 Thread 갯수를 작은 갯수로 유지하며 HTTP 요청을 동시 처리 할 수 있도록 함
    • HTTP 요청은 하나 이상의 Thread에 바인딩되어 처리될 수 있음

ThreadLocal

  • Thread 범위 변수 — 동일 Thread 내에서는 언제든 ThreadLocal 변수에 접근할 수 있음
// TheadLocal 변수를 생성
ThreadLocal<Integer> threadLocalValue = new ThreadLocal<>();
// ThreadLocal 변수에 값 쓰기
threadLocalValue.set(1);
// ThradLocal 변수에서 값 읽기
Integer result = threadLocalValue.get();
// ThreadLocal 변수 값 제거
threadLocal.remove();
  • Spring적 관점으로 동일 Thread내에서 실행되는 Controller, Service, Repository, 도메인 모델 어디에서든 명시적인 파라미터 전달 필요없이 ThreadLocal 변수에 접근할 수 있다.
  • ThreadPool과 함께 사용하는 경우 Thread가 ThreadPool에 반환되기 직전 ThreadLocal 변수 값을 반드시 제거해야함
  • 그렇지 않을 경우 아래와 같은 상황이 발생하고, 미묘한 버그가 생겨날 수 있음
    • 요청을 처리하기 위해 ThreadPool에서 Thread를 하나 가져옴
    • 요청 처리에 필요한 변수를 ThreadLocal에 set함
    • 요청 처리가 완료되고 Thread는 ThreadPool에 반환됨
    • 다른 요청을 처리하기 위해 ThreadPool에서 Thread를 하나 가져왔는데 이전 요청 처리에 사용된 ThreadLocal 변수가 남아있고, 이를 참조하여 잘못된 동작을 수행할 수 있음

SecurityContextHolder, SecurityContext, Authentication

SecurityContextHolder는 SecurityContext 데이터를 쓰거나 읽을수 있는 API를 제공 (기본 구현은 ThreadLocal를 이용함)

At the heart of Spring Security’s authentication model is the SecurityContextHolder. It contains the SecurityContext.

  • ThreadLocal을 기본 구현으로 사용한다는 것은 Thread Per Request 모델을 기본 고려했음을 의미함 (물론 Spring Security는 Webflux를 지원하기도 함)
    • FilterChainProxy 구현을 보면 finally 블록에서 SecurityContextHolder.clearContext() 메소드를 호출하는 확인할 수 있음
    • 이것은 HTTP 요청 처리가 완료되고 Thread가 ThreadPool에 반환되기전 ThreadLocal 변수 값을 제거하기 위함
  • SecurityContext
    • SecurityContextHolder 클래스를 통해 코드 어느 부분에서든 SecurityContext에 접근할 수 있음
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);
}
  • Authentication
    • 사용자를 표현하는 인증 토큰 인터페이스이며, 인증주체를 표현하는 Principal 그리고 사용자의 권한을 의미하는 GrantedAuthority 목록을 포함
      • AnonymousAuthenticationToken 클래스는 익명 사용자를 표현하기 위한 Authentication 인터페이스 구현체
      • UsernamePasswordAuthenticationToken 클래스는 로그인 아이디/비밀번호 기반 Authentication 인터페이스 구현체
      • RememberMeAuthenticationToken 클래스는 remember-me 기반 Authentication 인터페이스 구현체
    • 인증이 완료되거나 혹은 인증되지 사용자를 모두를 포괄적으로 표현하며, 인증 여부를 확인할 수 있음
      • 사용자의 인증 완료 여부에 따라 Principal 값이 달라짐
        • 로그인 전 Principal — 로그인 아이디 (String)
        • 로그인 후 Principal — org.springframework.security.core.userdetails.User 객체

인증(Authentication)처리

인증(Authentication) — 사용자가 주장하는 본인이 맞는지 확인하는 절차를 의미

  • 일반적으로 온라인상에서 수행되는 인증은 아이디/비밀번호를 입력하여 수행된다.
    어플리케이션은 아이디에 해당하는 사용자를 확인하고, 입력한 비밀번호가 저장된 비밀번호와 일치하는지 확인한다.

  • Authentication
    • 인증 주체, 사용자를 표현하는 객체. 인증 되기 전과 후 모두를 표현한다.
      그림의 노란색 Authentication는 인증 전의 사용자, 녹색 Authentication은 인증 후의 사용를 표현한다고 볼 수 있다.
    • Authentication 객체는 사용자의 권한을 의미하는 GrantedAuthority목록을 포함할 수 있다.

DefaultLoginPageGeneratingFilter

  • HTTP GET 요청에 대해 디폴트 로그인 페이지를 생성해주는 필터
    • 로그인 아이디/비밀번호/Remember-Me 파라미터명을 변경가능
  • 로그인 페이지 자체를 커스텀 구현 가능하며, 이 경우 해당 필터는 비활성화됨
http
	// ... 생략 ...
	.formLogin()
		.loginPage("/mylogin")
		.permitAll()
	  .and()
  // ... 생략 ...

AbstractAuthenticationProcessingFilter (a.k.a UsernamePasswordAuthenticationFilter)

  • AbstractAuthenticationProcessingFilter : 추상클래스
    의 하위 클래스 UsernamePasswordAuthenticationFilter
  • 사용자 인증을 처리하기 위한 필터로 가장 대표적인 것으로 UsernamePasswordAuthenticationFilter 구현체가 있음
  • 사용자 인증을 위한 정보(credentials)를 취합하고, Authentication 객체를 생성함
    • UsernamePasswordAuthenticationFilter 구현에서는 로그인 아이디/비밀번호를 취합하고, Authentication 인터페이스 구현체중 하나인UsernamePasswordAuthenticationToken 객체를 생성함
  • 인증이 완료되지 않은 Authentication 객체는 AuthenticationManager 객체로 전달됨
  • 인증이 정상적으로 완료된다면 새롭게 만들어진 Authentication 객체를 반환함
    • 여기서 새롭게 만들어진 Authentication 객체는 인증이 완료된 상태이고, GrantedAuthority 목록을 포함하고 있음
@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);
}

AuthenticationManager, ProviderManager

  • AuthenticationManager 인터페이스는 사용자 인증을 위한 API를 제공함
public interface AuthenticationManager {
	Authentication authenticate(Authentication authentication) throws AuthenticationException;
}
  • 기본 구현체로 org.springframework.security.authentication.ProviderManager 클래스가 있음
    • ProviderManager는 AuthenticationProvider를 리스트로, 1개 이상의 AuthenticationProvider로 구성됨
    • AuthenticationProvider 중 하나가 필터에서 만들었던 Authentication, 실질적으로는 UsernamePasswordAuthenticationToken. 그것을 처리할 수 있는 어선티케이션 프로바이더가 하나 있는데, 그것이 DaoAuthenticationProvider
    • 실질적으로 Provider가 인증처리를 완료하게되고, 인증이 완료되게되면 인증이 완료된 사용자를 표현하기 위한 Authentication 객체 생성. 권한을 적절하게 맵핑시켜주고 인증처리가 완료됨.
    • 1개 이상의 AuthenticationProvider 인터페이스 구현체 중 어떤 AuthenticationProvider가 실제 인증을 처리할지 결정할 수 있음
      • 주어진 Authentication 객체에 대해 supports(Class<?> authentication) 메소드가 true 를 반환하는 AuthenticationProvider 객체가 인증을 처리함
      • 예를 들어 UsernamePasswordAuthenticationToken 타입의 인증 요청은 DaoAuthenticationProvider가 처리함

  • ProviderManager는 AuthenticationProvider의 리스트이다.
  • 실질적으로는 AuthenticationProvider가 인증에 필요한 로직을 갖고 있다.

0개의 댓글