W16D1

Onni·2021년 11월 16일
0

TIL

목록 보기
20/21

1. Thread Per Request 모델

(1) Thread Per Request

  • WAS는 ThradPool을 생성함 (Tomcat 기본값 200)
  • HTTP 요청이 들어오면 Queue에 적재되고, ThreadPool 내의 특정 Thread가 Queue에서 요청을 가져와 처리하게됨
  • HTTP 요청은 처음부터 끝까지 동일한 Thread에서 처리됨
  • HTTP 요청 처리가 끝나면 Thread는 다시 ThreadPool에 반납됨
  • 즉, WAS의 최대 동시 처리 HTTP 요청의 갯수는 ThreadPool의 갯수와 같음
  • Thead 갯수를 늘리면 동시 처리 갯수가 늘어나지만, Thread Context 스위칭에 의한 오버헤드도 커지기 때문에 성능이 선형적으로 증가하지는 않음
  • 최근 소개된 WebFlux 같은 기술은 Thread 갯수를 작은 갯수로 유지하며 HTTP 요청을 동시 처리 할 수 있도록 함
    - HTTP 요청은 하나 이상의 Thread에 바인딩되어 처리될 수 있음
    https://happyer16.tistory.com/entry/대용량-트래픽을-감당하기-위한-Spring-WebFlux-도입

(2) ThreadLocal

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

스프링 mvc는 Thread Per Request 모델을 기반으로 하고 있고 클라이언트 요처을 처리하기 위해 쓰레드풀을 사용하고 있다. 쓰레드 로컬 변수를 사용할떄는 클라이언트 요청처리가 모두 완료된후에 변수를 clear 시켜줘야함

2. SecurityContextHolder

  • SecurityContextHolder는 SecurityContext 데이터를 쓰거나 읽을수 있는 API를 제공 (기본 구현은 ThreadLocal를 이용함)
    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);
    
    }
  • Authentication
    • 사용자를 표현하는 인증 토큰 인터페이스이며, 인증주체를 표현하는 Principal 그리고 사용자의 권한을 의미하는 GrantedAuthority 목록을 포함
      • AnonymousAuthenticationToken 클래스는 익명 사용자를 표현하기 위한 Authentication 인터페이스 구현체
      • UsernamePasswordAuthenticationToken 클래스는 로그인 아이디/비밀번호 기반 Authentication 인터페이스 구현체
      • RememberMeAuthenticationToken 클래스는 remember-me 기반 Authentication 인터페이스 구현체
    • 인증이 완료되거나 혹은 인증되지 사용자를 모두를 포괄적으로 표현하며, 인증 여부를 확인할 수 있음
      • 사용자의 인증 완료 여부에 따라 Principal 값이 달라짐
        • 로그인 전 Principal — 로그인 아이디 (String)
        • 로그인 후 Principal — org.springframework.security.core.userdetails.User 객체

SecurityContextHolder가 쓰레드로컬을 기반으로 하고 있다. -> spring MVF에서 컨트롤러, 레포지토리 , 등에서 SecurityContextHolder를통해 security를 조회할수 있따.

GrantedAuthorities 사용자가 어떤 권한을 가지고 있는지 반환하는 메서드
getPrincipal : 인증이 완료 됐거나 돼지않았어도 그 인증을 포괄적으로 표현 하게 해주
예를들어, 인증전에는 privipal이 리턴하는 타입은 로그인아이디 , 인증후에는 유저 객체
인증전과 인증후에 반환되는 타입이 서로 다르기때문에 Object로 반환형

isAuthenticated : 인증되었는지 아닌지 확인해줌

SecurityContextHolder은 쓰레드로컬 변수를 통해 SecurityContext를 담고 있고SecurityContext를는 Authentication이라는 인터페이스를 담고 있다.

3. DefaultLoginPageGeneratingFilter

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

    default 설정 값들을 아래와 같은 방법으로 커스텀 구현이 가능하다.

4. 인증 처리

  • 사용자 인증을 처리하기 위한 필터로 가장 대표적인 것으로 UsernamePasswordAuthenticationFilter 구현체가 있음
  • 사용자 인증을 위한 정보(credentials)를 취합하고, Authentication 객체를 생성함
    • UsernamePasswordAuthenticationFilter 구현에서는 로그인 아이디/비밀번호를 취합하고, Authentication 인터페이스 구현체중 하나인UsernamePasswordAuthenticationToken 객체를 생성함
  • 인증이 완료되지 않은 Authentication 객체는 AuthenticationManager 객체로 전달됨
  • 인증이 정상적으로 완료된다면 새롭게 만들어진 Authentication 객체를 반환함
    • 여기서 새롭게 만들어진 Authentication 객체는 인증이 완료된 상태이고, GrantedAuthority 목록을 포함하고 있음
  • UsernamePasswordAuthenticationFilter
    @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 클래스가 있음
    • 이 구현체는 1개 이상의 AuthenticationProvider 인터페이스 구현체로 구성됨
    • AuthenticationProvider 인터페이스 구현체가 실제 사용자 인증을 처리하게 됨
    • 1개 이상의 AuthenticationProvider 인터페이스 구현체 중 어떤 AuthenticationProvider가 실제 인증을 처리할지 결정할 수 있음
      • 주어진 Authentication 객체에 대해 supports(Class<?> authentication) 메소드가 true 를 반환하는 AuthenticationProvider 객체가 인증을 처리함
      • 예를 들어 UsernamePasswordAuthenticationToken 타입의 인증 요청은 DaoAuthenticationProvider가 처리함

  • AuthenticationProvider, 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 {
    		// ... 생략 ...
    		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;
    			}
    		}
    		// ... 생략 ...
    	}
    	// ... 생략 ...
    }

**정리

  1. ProcessRequest에서 id,password와 같은 정보를 가지고와서 인증되기전 사용자를 표현하는 Authentication객체를 만든다. Authentication객체는 인증되기전 이기때문에 권한목록도 없고, authenticateflag값도 false를 가지고 있다. principal부분에는 로그인 아이디 의미하는 String타입의 아이디를 가지고 있는다.

  2. 이 객체를 AuthenticationManager에 넘겨 AuthenticationManager을 통해 인증처리가 이루어진다. AuthenticationManager 인터페이스의 구현체중 하나인 prividerManager가 실제적으로 인증처리를 담당하는데,prividerManager가 가지고 있는 AuthenticationProvider를 List로 들고 있다.

  3. AuthenticationProvider중 하나가 Authentication을 처리할수 잇는 DaoAuthenticationProvider 를 하나 가지고 있다.
    AuthenticationProvider가 인증처리 완료휴 인증이된 사용자를 표현하기 위한 Authentication객체를 만들고 권한을 매핑시켜준후 인증처리 완료한다.

profile
꿈꿈

0개의 댓글