백엔드 데브코스 TIL 40일차

Inchang Choi·2022년 5월 23일
0

백엔드 데브코스 TIL

목록 보기
24/30
post-thumbnail

학습목표

강의를 들으며 내가 알고 있는 내용을 점검하고,

새로 배운 내용을 정리하며,

궁금한 내용을 알아가며 학습해나가는 것을 목표로 합니다.

Spring Security Architecture

Conceptual Architecture

거시적인 관점에서 Spring Security는 웹 요청을 가로챈 후 사용자를 인증하고, 인증된 사용자가 적절한 권한을 지니고 있는지 확인합니다.

그 중 인증과 인가를 담당하는 핵심 모듈은 다음과 같습니다.

  • AuthenticationManager 사용자 인증 관련 처리를 수행합니다.
  • AccessDecisionManager 사용자가 보호받는 리소스에 접근할 수 있는 적절한 권한이 있는지 확인합니다.

FilterChainProxy (Spring Security 필터 체인) 소개

Spring Security

Spring Security의 실질적인 구현은 자바에서 표준으로 제공하고 있는 서블릿 필터 (javax.servlet.Filter Interface 구현체)를 통해 이루어집니다.

서블릿 필터는 웹 요청을 가로챈 후 전처리 또는 후처리를 수행하거나, 요청 자체를 리다이렉트 합니다.

스프링 시큐리티의 이러한 서블릿 필터들의 모음을 FilterChainProxy라고 부릅니다.

FilterChainProxy

FilterChainProxy 세부 내용은 WebSecurityConfigurerAdapter 추상 클래스를 상속하는 구현체에서 설정합니다. (보통 @EnableWebSecurity 어노테이션도 함께 사용합니다.)

웹 요청은 이러한 필터 체인을 차례로 통과하게 됩니다.

웹 요청은 모든 필터를 통과하게 되지만, 모든 필터가 동작하는 것은 아닙니다.

각 필터는 웹 요청에 따라 동작 여부를 결정할 수 있고, 동작할 필요가 없다면 다음 필터로 웹 요청을 즉시 넘깁니다.

요청을 처리하고 응답을 반환하면 필터 체인 호출 스택은 모든 필터에 대해 역순으로 진행됩니다.

보통 springSecurityFilterChain 이라는 이름으로 Bean 등록됩니다.

그렇다면 웹 요청은 어떻게 FilterChainProxy로 전달될까?

DelegatingFilterProxy

웹 요청을 수신한 서블릿 컨테이너는 해당 요청을 DelegatingFilterProxy (javax.servlet.Filter 인터페이스 구현체) 로 전달합니다.

@Bean
@ConditionalOnBean(name = DEFAULT_FILTER_NAME)
public DelegatingFilterProxyRegistrationBean securityFilterChainRegistration(SecurityProperties securityProperties) {
	DelegatingFilterProxyRegistrationBean registration = new DelegatingFilterProxyRegistrationBean(DEFAULT_FILTER_NAME);
	registration.setOrder(securityProperties.getFilter().getOrder());
	registration.setDispatcherTypes(getDispatcherTypes(securityProperties));
	return registration;
}

DelegatingFilterProxy Bean은 SecurityFilterAutoConfiguration 클래스에서 자동으로 등록됩니다.

DelegatingFilterProxy는 실제적으로 웹 요청을 처리할 Target Filter Bean을 지정해야합니다.

Target Filter Bean은 바로 앞에서 알아본 FilterChainProxy입니다.

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException {
	// Lazily initialize the delegate if necessary.
	Filter delegateToUse = this.delegate;
	if (delegateToUse == null) {
		synchronized (this.delegateMonitor) {
			delegateToUse = this.delegate;
			if (delegateToUse == null) {
				WebApplicationContext wac = findWebApplicationContext();
				if (wac == null) {
					throw new IllegalStateException("No WebApplicationContext found: " +
							"no ContextLoaderListener or DispatcherServlet registered?");
				}
				delegateToUse = initDelegate(wac);
			}
			this.delegate = delegateToUse;
		}
	}
	// Let the delegate perform the actual doFilter operation.
	invokeDelegate(delegateToUse, request, response, filterChain);
}

protected Filter initDelegate(WebApplicationContext wac) throws ServletException {
	String targetBeanName = getTargetBeanName();
	Assert.state(targetBeanName != null, "No target bean name set");
	Filter delegate = wac.getBean(targetBeanName, Filter.class);
	if (isTargetFilterLifecycle()) {
		delegate.init(getFilterConfig());
	}
	return delegate;
}

protected void invokeDelegate(Filter delegate, ServletRequest request, ServletResponse response, FilterChain filterChain)
		throws ServletException, IOException {
	delegate.doFilter(request, response, filterChain);
}

FilterChainProxy를 구성하는 Filter 목록

  • 정말 다양한 필터 구현을 제공합니다.
  • 결국 Spring Security를 잘 이해하고 활용한다는 것은 이들 Filter를 이해하고, 적절하게 사용한다는 것을 의미합니다.

Spring Security Reference

RequestCacheAwareFilter

RequestCacheAwareFilter를 통해서 인증 요청에 가로채어진 원래 요청으로 이동합니다.

익명 사용자가 보호 받는 리소스 (예: /me)에 접근할 경우 접근 권한이 없기 때문에 AccessDecisionManager 에서 접근 거부 예외가 발생합니다.

ExceptionTranslationFilter 접근 거부 예외를 처리합니다.

  • 현재 사용자가 익명 사용자라면, 보호 받는 리소스로의 접근을 캐시처리하고, 로그인 페이지로 이동 시킴
private void handleAccessDeniedException(HttpServletRequest request, HttpServletResponse response,
		FilterChain chain, AccessDeniedException exception) throws ServletException, IOException {
	Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
	boolean isAnonymous = this.authenticationTrustResolver.isAnonymous(authentication);
	if (isAnonymous || this.authenticationTrustResolver.isRememberMe(authentication)) {
		if (logger.isTraceEnabled()) {
			logger.trace(LogMessage.format("Sending %s to authentication entry point since access is denied",
					authentication), exception);
		}
		sendStartAuthentication(request, response, chain,
				new InsufficientAuthenticationException(
						this.messages.getMessage("ExceptionTranslationFilter.insufficientAuthentication",
								"Full authentication is required to access this resource")));
	}
	else {
		if (logger.isTraceEnabled()) {
			logger.trace(
					LogMessage.format("Sending %s to access denied handler since access is denied", authentication),
					exception);
		}
		this.accessDeniedHandler.handle(request, response, exception);
	}
}

protected void sendStartAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
		AuthenticationException reason) throws ServletException, IOException {
	// SEC-112: Clear the SecurityContextHolder's Authentication, as the
	// existing Authentication is no longer considered valid
	SecurityContextHolder.getContext().setAuthentication(null);
	this.requestCache.saveRequest(request, response);
	this.authenticationEntryPoint.commence(request, response, reason);
}
  • RequestCacheAwareFilter를 통해 위에서 살펴본 캐시된 요청을 처리할 수 있습니다.
    • 캐시된 요청이 있다면 캐시된 요청을 처리하고, 캐시된 요청이 없다면 현재 요청을 처리함

      @Override
      public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
      		throws IOException, ServletException {
      	HttpServletRequest wrappedSavedRequest = this.requestCache.getMatchingRequest((HttpServletRequest) request,
      			(HttpServletResponse) response);
      	chain.doFilter((wrappedSavedRequest != null) ? wrappedSavedRequest : request, response);
      }
      ```![](https://velog.velcdn.com/images/justsaturday/post/e6d8563a-d719-4ec3-ab9f-8b13e9119a7f/image.png)
profile
always positive

0개의 댓글