Spring Security

Spring Security에 대해 공부하고 정리한 내용입니다.

What is a Spring Security ?

Spring Security란 보안 솔루션을 제공해주는 Spring 기반의 스프링 하위 프레임워크이다.

Spring Security에서 제공해주는 보안 솔루션을 사용하면 개발자가 보안 관련 로직을 짤 필요가 없어 굉장히 간편하다.

🔗인증과 인가의 개념이 부족하다면 공부하고오는 것을 추천한다.

필터에서 동작한다.

Spring에서 클라이언트의 요청은 Filter를 타고 Servlet(Spring MVC에서 DispatcherServlet)으로 도달하게 되는데, Spring Security는 Servlet 도달하기 이전에 서블릿 필터 기반에서 동작을 한다. 따라서 단일 HTTP request에 대한 핸들링을 한다.

Spring Security 동작 위치

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
    // do something before the rest of the application
    chain.doFilter(request, response); // invoke the rest of the application
    // do something after the rest of the application
}

Filter 는 위에와 같이 체이닝의 형태로 여러 필터를 통과하여 Servlet에 도달한다.

Spring에서는 FilterDelegatingFilterProxy를 이용해서 추가할 수 있는데 Spring Security는 DelegatingFilterProxy를 이용해서 Spring 컨테이너에 생성된 FilterChainProxy bean을 이용하여 SecurityFilter를 구성하는 SecurityFilterChain을 만든다. (springSecurityFilterChain 이름으로 생성된 빈을 ApplicationContext에서 찾아 위임을 요청)

Spring Security와 SecurityFilterChain

실질적으로 FilterChainProxy에서 보안을 처리한다. 결과적으로 SecurityFilterChain을 통해 URL에 따른 인증과 인가 등의 보안 처리가 된다.

위에 사진을 보면 /api/messages/ URL의 요청이 온다면 SecurityFilterChain 0번째 필터를 타게되고 /messages/ URL의 요청이 온다면 필터 체인을 0번째부터 쭉 찾다가 마지막 남은 SecurityFilterChain n번째 필터에 타게된다.

인증과 인가 예외처리

SecurityFilterChainExceptionTranslationFilter 는 잘못된 권한에 대한 AccessDeniedExceptionAuthenticationException 예외의 응답을 다룬다.

인증과 인가 예외처리

// ExceptionTranslationFilter pseudocode

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

ExceptionTranslationFilter pseudocode를 보면 단번에 이해가 쉽다. ExceptionTranslationFilter에서 다음 필터체인으로 넘겨줄때(doFilter) try-catch로 이후 필터에서 일어나는 인가와 인증에 대한 예외를 받는 방식이다.

인증(Authentication)

Spring Security의 인증에 사용되는 아키텍처 컴포넌트을 살짝 살펴보면 다음과 같다.

  • SecurityContextHolder

    Spring Security가 누가 인증됐는 지의 세부정보들을 저장하는 곳이다.

  • SecurityContext

    SecurityContextHolder로 부터 얻게된 현재 인증된 유저의 Authentication를 포함하고 있는 애플리케이션 경계이다.

  • Authentication

    한 유저가 인증하기 위해 제공한 credential(password 같은 자격을 의미) 또는 SecurityContext로부터 현재 유저를 제공하기 위한 AuthenticationManager의 인풋 값이다.

  • GrantedAuthority

    Authentication를 통해 인증된 주체(principal)에 대한 권한

  • AuthenticationManager

    Spring Security의 필터에서 인증을 수행하는 API

  • ProviderManager

    AuthenticationManger의 가장 일반적인 구현체

  • Request Credentials with AuthenticationEntryPoint

    클라이언트로부터 credentials(자격)을 요청할 때 사용된다.

  • AbstractAuthenticationProcessingFilter

    인증을 위해 기반이 되는 필터. 높은 수준의 인증 흐름과 조각들이 어떻게 함께 작동하는지 나타나있다.

SecurityContextHolder, SecurityContext

SecurityContextHolder는 Spring Security 인증 모델의 중심이며 SecurityContext를 포함하고있다.

SecurityContextSecurityContextHolder로 부터 얻을 수 있으며 인증(Authentication) 객체를 포함한다.

SpringContextHolder

값이 채워진 경우 SpringContextHolder는 현재 인증된 사용자로 사용된다.

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

SecurityContextHolder.setContext(context); 

위에 예제처럼 빈 SecurityContext를 만들고 아주 간단한 인증 토큰을 생성해서 SecurityContext에 넣어준다음 이 Context를 SpringContextHolder에 넣음으로써 SpringSecurity는 인가를 위한 정보로 사용할 것이다.

따라서 인증된 주체(principal)에 대한 정보를 얻고싶다면 아래와 같이 SecurityContextHolder를 사용하면된다.

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

SecurityContextHolder는 default로 ThreadLocal를 이용하여 인증된 유저의 세부사항들을 저장한다. 이는 인자로 메소드에 값을 넘겨주지 않아도 SecurityContext는 항상 같은 스레드에서 메소드를 호출할 수 있다는 것을 의미한다.

SpringSecurity에서는 ThreadLocal을 안전하게 사용하기 위해서 FilterChainProxySecurityContext를 항상 지우는 것을 보장한다.

이어서 2편에서 진행하겠습니다.

Reference

https://docs.spring.io/spring-security/site/docs/current/reference/html5/#servlet-hello

⤧  Next post 42서울 오픈스튜디오 프로젝트 개발기 - 6 ⤧  Previous post 진정한 REST API