Skip to content

Latest commit

 

History

History
751 lines (638 loc) · 43.6 KB

3.security-core.md

File metadata and controls

751 lines (638 loc) · 43.6 KB

3. 시큐리티 주요 아키텍처 이해

3.1. Proxy

웹 보안

  • 웹 계층의 스프링 보안은 서블릿 기반으로 한다.

  • 클라이언트는 응용 프로그램에 요청을 보낸다.

  • 컨테이너는 요청 URI의 경로를 기반으로 어떤 필터와 어떤 서블릿이 응용 프로그램에 적용되는지 결정한다.

  • 서블릿은 한번의 요청을 필터체인을 순서대로 싷행하여 처리한다.

3.1.1. DelegatingFilterProxy & FilterChainProxy

  • DelegatingFilterProxy & FilterChainProxy 생성 및 등록 Flow

    • SecurityFilterAutoConfiguration
      • 63 Line: DelegatingFilterProxyRegistrationBean registration = new DelegatingFilterProxyRegistrationBean(DEFAULT_FILTER_NAME);
        • springSecurityFilterChain 이름으로 DelegatingFilerProxy 를 생성
      • DelegatingFilterProxy
        • 165 Line: public DelegatingFilterProxy(String targetBeanName, @Nullable WebApplicationContext wac) {
          • springSecurityFilterChain: DelegatingFilerProxy 형태로 Filter 생성
      • WebSecurityConfiguration (스프링 영역)
        • 94 Line: @Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
          • springSecurityFilterChain 명으로 Bean 등록
        • 95 Line: public Filter springSecurityFilterChain() throws Exception {
          • webSecurity가 FilterChainProxy을 springSecurityFilterChain이라는 이름으로 생성
      • WebSecurity
        • 279 Line: protected Filter performBuild() throws Exception {
        • 296 Line: FilterChainProxy filterChainProxy = new FilterChainProxy(securityFilterChains);
          • FilterChainProxy를 Bean생성 및 등록
  • DelegatingFilterProxy & FilterChainProxy 요청 Flow

    • DelegatingFilterProxy
      • 250 Line: public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
        • 위임할 Filter를 찾는다. (springSecurityFilterChain)
      • 264 Line: delegateToUse = initDelegate(wac);
        • 338 Line: Filter delegate = wac.getBean(targetBeanName, Filter.class);
          • AnnotationConfigServletWebServerApplicationContext으로부터 getBean() 하여 springSecurityFilterChain라는 이름의 Bean을 가져옴
          • 가져온 Bean은 FilterChainProxy
      • 271 Line: invokeDelegate(delegateToUse, request, response, filterChain);
        • 요청을 위임
        • 358 Line: delegate.doFilter(request, response, filterChain);
          • FilterChainProxy를 호출
    • FilterChainProxy
      • 172 Line: public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        • DelegatingFilterProxy의 request를 전달
        • 보안 처리를 실시함
      • 190 Line: private void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        • securityFilter의 리스트를 가져와 보안 처리를 수행
        • 198 Line: List filters = getFilters(fwRequest);
          • securityFilter 리스트를 호출
        • 214 Line: VirtualFilterChain vfc = new VirtualFilterChain(fwRequest, chain, filters);
          • FilterChain 내부적으로 가상 VirtualFilterChain을 생성하여 내부적으로 실질적으로 request 처리를 수행함
          • VirtualFilterChain
            • 325 Line: Filter nextFilter = additionalFilters.get(currentPosition - 1);
              • 설정된 securtyFilterChain을 관리 및 호출하여 request에 대한 보안처리를 수행함
              • 요청을 완로하고나서 Servlet으로 이동
  • DelegatingFilterProxy 정리

    • 필터는 서블릿 2.3 이상 버전부터 사용할 수 있다.

    • 서블릿 필터란 사용자가 요청에 대한 자원에 접근 전 / 후에 대한 작업 처리가 가능하다.

    • 대신 스프링 기술을 사용할 수 없음

    • 스프링 시큐리티는 필터로 구성되어 처리를 하고 있다.

    • 스프링내에 필터 개발 목적

      • 필터에서 스프링 기술을 사용하기 위한 필요성이 생김
      • 스프링 빈 객체를 생성하고 필터라는 타입으로 다루어짐
    • 서블릿 기반으로 동작하는 스프링은 사용자의 요청을 서블릿 필터가 먼저 받게 된다.

    • Was에 Tomcat이 올라가게되면 서블릿 컨테이너에서 돌아가고 있는 필터가 사용자의 요청을 받게 된다.

    • 서블릿 필터에서 스프링에서 생성한 필터 타입의 스프링 빈으로 요청 정보를 넘겨 처리할 수 있게하는 클래스가 DelegatingFilterProxy 이다.

    • DelegatingFilterProxy 는 서블릿 필터에 있는 필터이다.

    • DelegatingFilterProxy는 사용자의 요청을 Spring에서 관리하는 Filter (Bean) 에게 작업처리를 위임하는 역할을 하고 있다.

    • 결론

      • 모든 요청은 서블릿 필터가 받고, 스프링 기술을 사용하기 위해서 스프링 시큐리티라는 필터를 통해 보안처리 및 스프링 기술을 사용할수 있게 되었다.
    • springSecurityFilterChain이라는 이름으로 생성되고 이 Bean은 ApplicationContext가 관리

    • springSecurityFilterChain으로 생성되는 필터 Bean은 FilterChainProxy이다.

  • FilterChainProxy

    • springSecurityFilterChain 내에 필터들을 관리하고 제어
    • 필요에 따라 Filter의 개수가 늘어날 수 있음
    • 사용자 정의에 따라 생성하여 필터체인에 추가 가능
    • 필터의 순서가 중요
    • 마지막 필터의 역할은 인증 및 인가 검증 처리
    • 필터가 모두 정상 처리되어야 resource에 접근할 수 있다.
  • 기본 필터 체인 0 = {WebAsyncManagerIntegrationFilter@7067} 1 = {SecurityContextPersistenceFilter@8025} 2 = {HeaderWriterFilter@8026} 3 = {CsrfFilter@8027} 4 = {LogoutFilter@8028} 5 = {UsernamePasswordAuthenticationFilter@8029} 6 = {DefaultLoginPageGeneratingFilter@8030} 7 = {DefaultLogoutPageGeneratingFilter@8031} 8 = {BasicAuthenticationFilter@8032} 9 = {RequestCacheAwareFilter@8033} 10 = {SecurityContextHolderAwareRequestFilter@8034} 11 = {AnonymousAuthenticationFilter@8035} 12 = {SessionManagementFilter@8036} 13 = {ExceptionTranslationFilter@8037} 14 = {FilterSecurityInterceptor@8014}

  • 내가 확인해 본 내용

    1. 시큐리티 설정만 했을 때 적용되는 필터의 내용 ?
    2. 어플리케이션 실행 시 DelegatingFilterProxy 및 FilterChainProxy 가 등록되는 플로우
    3. 사용자 요청 시 처리되는 플로우

DelegatingFilterProxy

3.2. 필터 초기화와 다중 보안 설정

3.2.1. 필터 초기화 및 다중 설정 클래스

  • 다중 설정이 필요한 이유 ?

    • 요청 방식 또는 요청 URL 에 따라 다른 보안 방식이 필요하는 경우에 사용한다.
  • 다중 설정 시 처리 방식

    • requestMatcher 에 따라 다른 Filter 사용
  • 다중 설정 시 설정 순서

    • requestMatcher에 따라 Filter Flow가 다르기 때문에 상세한 requestMapping 부터 선언하도록 한다.
    • 넓은 범위의 requestMapping부터 선언 시 세부적으로 설정해야 할 경로의 보안이 무시되는 이슈가 발생할 수 있음
  • 하나의 프로젝트에 securityFilterChains라는 리스트에 securityFilterChain이라는 값으로 존재

@Order(0)
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http.
                /* 3.1.1. 필터 초기화 및 다중 보안 설정 용 */
                .antMatcher("/admin/**")
                .authorizeRequests()
                .anyRequest().authenticated()
            .and()
                .httpBasic();
    }
}
@Order(1)
@Configuration
public class SecuritySubConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        /* 3.1.1. 필터 초기화와 다중 보안 설정 */
        http
                .authorizeRequests()
                .anyRequest().permitAll()
            .and()
                .formLogin();
    }
}
  • WebSecurity
    • 296 Line: FilterChainProxy filterChainProxy = new FilterChainProxy(securityFilterChains);
      • securityFilterChains 라는 값 안에 두가지 filters 값이 들어있음
      • FilterChainProxy 생성 시 생성자로 넘김
      • antMatchers의 resource 접근 값에 따라 필터 프로세스가 달라짐
        • chain: 1 0 = {WebAsyncManagerIntegrationFilter@7506} 1 = {SecurityContextPersistenceFilter@7507} 2 = {HeaderWriterFilter@7508} 3 = {CsrfFilter@7509} 4 = {LogoutFilter@7510} 5 = {BasicAuthenticationFilter@7511} 6 = {RequestCacheAwareFilter@7512} 7 = {SecurityContextHolderAwareRequestFilter@7513} 8 = {AnonymousAuthenticationFilter@7514} 9 = {SessionManagementFilter@7515} 10 = {ExceptionTranslationFilter@7516} 11 = {FilterSecurityInterceptor@7517}
        • chain: 2 0 = {WebAsyncManagerIntegrationFilter@7522} 1 = {SecurityContextPersistenceFilter@7523} 2 = {HeaderWriterFilter@7524} 3 = {CsrfFilter@7525} 4 = {LogoutFilter@7526} 5 = {UsernamePasswordAuthenticationFilter@7527} 6 = {DefaultLoginPageGeneratingFilter@7528} 7 = {DefaultLogoutPageGeneratingFilter@7529} 8 = {RequestCacheAwareFilter@7530} 9 = {SecurityContextHolderAwareRequestFilter@7531} 10 = {AnonymousAuthenticationFilter@7532} 11 = {SessionManagementFilter@7533} 12 = {ExceptionTranslationFilter@7534} 13 = {FilterSecurityInterceptor@7535}
      • FilterChainProxy
        • 198 Line: List filters = getFilters(fwRequest);
          • 설정된 필터 리스트를 가져옴
        • 226 Line: if (chain.matches(request)) {
          • 요청정보(antMatcher)와 매핑이되는 해당 filterChain을 찾음

3.3. Authentication

  • Authentication
    • 사용자의 인증 정보를 저장하는 토큰 개념
      • 인증 시 Id와 Password를 담고 인증 검증을 위해 전달되어 사용된다.
      • 인증 후 최종 적으로 인증 결과(User, 권한정보)를 담고 SecurityContext에 저장되어 전역적으로 참조가 가능하다.
Authentication authentication = SecurityContextHolder.getContext().getAuthentication()
  • 구조
    • principal: 사용자 아이디 혹은 User 객체를 저장
    • credential: 사용자 비밀번호 (인증 정보)
    • authorities: 인증된 사용자의 권한 목록
    • details: 인증 부가 정보
    • Authenticated: 인증 여부

Authentication 인증 Flow

3.4. SecurityContextHolder, SecurityContext

  • SecurityContext

    • Authentication 객체가 저장되는 보관소로 필요시 언제든지 Authentication 객체를 꺼내어 쓸 수 있도록 제공되는 클래스
    • ThreadLocal 에 저장되어 아무 곳에서나 참조가 가능하도록 설계됨
    • 인증이 완료되면 HttpSession 에 저장되어 어플리케이션 전반에 걸쳐 전역적인 참조가 가능하다.
  • SecurityContextHolder

    • SecurityContext 객체 저장 방식
      • MODE_THREADLOCAL: 스레드당 SecurityContext 객체를 할당, 기본값
      • MODE_INHERITABLETHREADLOCAL: 메인 스레드와 자식 스레드에 관하여 동일한 SecurityContext를 유지
        • 기본적으로는 메인 스레드와 자식 스레드간 영역이 달라 공유하고 있지 않음
      • MODE_GLOBAL: 응용 프로그램에서 단 하나의 SecurityContext 를 저장 (static 변수로 저장)
    • SecurityContextHolder.clearContext() : SecurityContext 기존 정보 초기화
  • 시나리오 정리

    • MainThread와 SubThread의 ThreadLocal의 영역이 독립적인 것을 확인하기 위한 시나리오
      • SecurityContextHolder의 기본 전략 모드인 ThreadLocal과 InheritableThreadLocal 모드를 비교하여 Thread 간 SecurityContext가 관리되는 방식을 확인
    @GetMapping("/")
    public String index(HttpSession session) {

        // 1
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        // 2
        SecurityContext context = (SecurityContext) session.getAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY);
        Authentication sessionContext = context.getAuthentication();

        return "Hello World";
    }

    @GetMapping("/thread")
    public String thread() {
        new Thread(
                new Runnable() {
                    @Override
                    public void run() {
                        /* ThreaLocal 모드에서는 인증 객체가 null, InheritableThreadLocal 모드의 경우 Thread간 인증 객체가 공유 가능 한 것을 확인 */
                        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
                    }
                }
        ).start();
        return "thread";
    }

SecurityContext

3.4.1. SecurityContextHolder

  • SecurityContextHolder

    • 96 Line: private static void initialize() { ... }
      • SecurityContext 객체를 저장하는 방식
        1. MODE_THREADLOCAL

          • 각 Thread간 ThreadLocal에 저장되어 있는 SecurityContext는 또 다른 Thread와 독립적으로 관리한다.
        2. MODE_INHERITABLETHREADLOCAL

          • MainThread내에 SubThread간 ThreadLocal이 공유되어 SecurityContextHolder에 접근하여 동일한 SecurityContext를 저장 및 호출할 수 있다.
        3. MODE_GLOBAL : GlobalSecurityContextHolderStrategy

      • xxxSecurityContextHolderStrategy 라는 각각의 전략 클래스명으로 SecurityContext 를 관리
  • SecurityContextHolder 요약

    1. SecurityContext 관리

      • clearContext
      • getContext
      • setContext
      • createEmptyContext
    2. 세 가지 SecurityContextStrategy의 구현체를 설정 가능

      • MODE_THREADLOCAL : ThreadLocalSecurityContextHolderStrategy
      • MODE_INHERITABLETHREADLOCAL : InheritableThreadLocalSecurityContextHolderStrategy
      • MODE_GLOBAL : GlobalSecurityContextHolderStrategy

3.4.2. SecurityContext

  • SecurityContext (interface)

    • SecurityContext 객체에 접근하는 방법 2가지

      1. SecurityContextHolder
      2. HttpSession
    • SecurityContextHolder에 있는 SecurityContext에 접근하는 방법

      1. Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
      2. HttpSession 에 있는 SecurityContext에 접근하는 방법
        • SecurityContext context = (SecurityContext) session.getAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY)
        • Authentication authentication = context.getAuthentication();
  • SecurityContextImpl (class)

  • SecurityContext 요약

    1. Authentication 관리
      • equals
      • hashCode
      • toString
      • getAuthentication
      • setAuthentication

3.5. SecurityContextPersistenceFilter

  • SecurityContext 객체의 생성, 저장, 조회

    1. 익명 사용자를 처리하는 방식

      • 새로운 SecurityContext 객체를 생성하여 SecurityContextHolder 에 저장
      • AnonymousAuthenticationFilter에서 AnonymousAuthenticationToken 객체를 SecurityContext 에 저장
    2. 익명 사용자가 인증 시

      • 새로운 SecurityContext 객체를 생성하여 SecurityContextHolder에 저장
      • form인증 방식의 UsernamePasswordAuthenticationFilter 에서 인증 성공 후 SecurityContext 에 UsernamePasswordAuthenticationToken 객체를 SecurityContext에 저장
      • 인증이 최종 완료되면 Session 에 SecurityContext을 저장
    3. 인증 후

      • HttpSession에서 SecurityContext 내에 Authentication을 꺼내어 SecurityContextHolder에 저장
      • SecurityContext 안에 Authentication 객체가 존재하면 계속 인증을 유지
    4. 최종 응답 시 공통

      • SecurityContextHolder.clearContext()
        • 매 요청마다 SecurityContextHolder의 SecurityContext를 비워준다.
  • 익명 사용자를 처리하는 Flow 정리

    • SecurityContextPersistenceFilter
      • 100 Line: SecurityContext contextBeforeChainExecution = repo.loadContext (holder);

        • 사용자가 이전에 인증에 성공하여 securityContext를 session에 저장한 정보가 있는지 확인
        • HttpSessionSecurityContextRepository
          • 108 Line: public SecurityContext loadContext(httpRequestResponseHolder requestResponseHolder) { ... }
            • request, response, session을 생성 session으로부터 SecurityContext의 존재여부를 확인 및 조회
          • 120 Line: context = generateNewContext();
            • 익명의 사용자는 인증한 이력이 없기 때문에 securityContext를 생성

            • 224 Line: protected SecurityContext generateNewContext()

            • 225 Line: SecurityContextHolder.createEmptyContext()

              • SecurityContextHolder
                • 161 Line: return strategy.createEmptyContext();
                  • InheritableThreadLocalSecurityContextHolderStrategy
                    • 60 Line: return new SecurityContextImpl();
                    • SecurityContext를 생성
                    • 54 Line: public void setContext(SecurityContext context);
                    • 56 Line: contextHolder.set(context);
                    • SecurityContext를 저장
      • 103 Line: SecurityContextHolder.setContext(contextBeforeChainExecution);

        • 익명 사용자의 요청 정보로 인하여 생성된 SecurityContext를 SecurityContextHolder에 저장
      • 105 Line: chain.doFilter(holder.getRequest(), holder.getResponse);

        • SecurityContext를 생성 및 저장한 뒤 다음 필터로 이동
      • AnonymousAuthenticationFilter

        • 95 Line: if(SecurityContextHolder.getContext().getAuthentication() == null) { ... }
          • 익명 사용자의 접근으로 인하여 Authentication 값이 null 이기 때문에 익명 사용자용 인증처리 로직이 실행
        • 114 Line: AnonymousAuthenticationToken auth = new AnonymousAuthenticationFilter(key, principal, authorities);
          • 익명 객체 토큰을 생성
        • 96 Line: SecurityContextHolder.getContext.setAuthentication(createAuthentication((HttpServletRequest) req));
          • 바로 위에서 만든 익명 객체 토큰 값으로 AnonymousAuthentication을 생성하여 SecuriryContextHodler의 SecurityContext에 Authentication으로 저장
      • 113 Line: SecurityContextHolder.clearContext();

        • 익명 사용자의 경우 SecurityContext를 별도로 저장하지 않고 응답 하기전에 SecurityContextHolder 내에 SecurityContext를 삭제한다.
        • 매 요청바다 SecurityContext를 생성, SecurityContextHolder에 저장하여 Filter간 인증 정보를 확인하기 때문에 계속 저장할 필요가 없음
      • 114 Line: repo.saveContext(contextAfterChainExecution, holder.getRequest(), holder.getResponse());

      • 134 Line: public void saveContext(SecurityContext context, HttpServletRequest request, HttpServletResponse response) { ... }

  • 익명 사용자를 처리하는 Flow

SecurityContextPersistenceFilter_AnonymousCase

  • 인증 요청 사용자를 처리 flow 정리

    • SecurityContextPersistenceFilter

      • 100 Line: SecurityContext contextBeforeChainExecution = repo.loadContext (holder);

        • 사용자가 이전에 인증에 성공하여 securityContext를 session에 저장한 정보가 있는지 확인
        • HttpSessionSecurityContextRepository
          • 108 Line: public SecurityContext loadContext(httpRequestResponseHolder requestResponseHolder) { ... }
            • request, response, session을 생성 session으로부터 SecurityContext의 존재여부를 확인 및 조회
          • 120 Line: context = generateNewContext();
            • 처음 인증하는 사용자는 인증한 이력이 없기 때문에 securityContext를 생성
            • 224 Line: protected SecurityContext generateNewContext()
            • 225 Line: SecurityContextHolder.createEmptyContext()
              • SecurityContextHolder
                • 161 Line: return strategy.createEmptyContext();
                  • InheritableThreadLocalSecurityContextHolderStrategy
                    • 60 Line: return new SecurityContextImpl();
                    • SecurityContext를 생성
                    • 54 Line: public void setContext(SecurityContext context);
                    • 56 Line: contextHolder.set(context);
                    • SecurityContext를 저장
      • 103 Line: SecurityContextHolder.setContext(contextBeforeChainExecution); - 인증되지 않은 사용자의 요청 정보로 인하여 생성된 SecurityContext를 SecurityContextHolder에 저장

      • 105 Line: chain.doFilter(holder.getRequest(), holder.getResponse);

        • SecurityContext를 생성 및 저장한 뒤 다음 필터로 이동
      • ... 인증 필터가 인증 처리를 한 뒤 인증 성공 후

      • AbstractAuthenticationProcessingFilter

        • 307 Line: protected void successfulAuthentication( ... ) { ... }
          • 인증 필터에서 인증 처리 후 인증 성공 시 처리되는 메서드
          • 316 Line: SecurityContextHolder.getContext().setAuthentication(authRequest);
            • 인증이 성공된 인증 객체를 SecurityContext에 저장
      • HttpSessionSecurityContextRepository

        • 344 Line: protected void saveContext (SecurityContext context) { ... }
          • 인증이 최종 완료되면 Session에 SecurityContext를 저장
        • 349 Line: if(authentication == null || trustResolver.isAnonymous(authentication)) { ... }
          • 익명 사용자가 아닌경우
        • 368 Line: if(httpSession != null) { ... }
        • 373 Line: httpSession.setAttribute(springSecurityContextKey, context);
          • HttpSession 에 SecurityContext를 저장
      • 113 Line: SecurityContextHolder.clearContext();

        • 응답 하기전에 SecurityContextHolder 내에 SecurityContext를 삭제한다.
        • 매 요청바다 SecurityContext를 생성, SecurityContextHolder에 저장하여 Filter간 인증 정보를 확인하기 때문에 계속 저장할 필요가 없음
      • 114 Line: repo.saveContext(contextAfterChainExecution, holder.getRequest(), holder.getResponse());

      • 134 Line: public void saveContext(SecurityContext context, HttpServletRequest request, HttpServletResponse response) { ... }

      • 정상 인증 처리된 Authentication은 SecurityContext에 저장되어 Session이 관리하게 되는 형태
  • 인증 요청 사용자를 처리하는 Flow

SecurityContextPersistenceFilter_UnAuthenticationUser

  • 정상 인증 처리된 사용자가 요청 flow 정리

    • SecurityContextPersistenceFilter
      • 100 Line: SecurityContext contextBeforeChainExecution = repo.loadContext (holder);

        • 사용자가 이전에 인증에 성공하여 securityContext를 session에 저장한 정보가 있는지 확인
        • HttpSessionSecurityContextRepository
          • 108 Line: public SecurityContext loadContext(httpRequestResponseHolder requestResponseHolder) { ... }
            • request, response, session을 생성 session으로부터 SecurityContext의 존재여부를 확인 및 조회
      • 103 Line: SecurityContextHolder.setContext(contextBeforeChainExecution); - 인증된 사용자의 정보는 SecurityContext에 이미 존재하여 현재 필터에서 사용될 SecurityContextHolder에 저장되어 공유할 수 있도록 한다.

      • 105 Line: chain.doFilter(holder.getRequest(), holder.getResponse);

        • 공유할 SecurityContext를 Holder에 저장한 뒤 다음 필터로 이동
      • 113 Line: SecurityContextHolder.clearContext();

        • 응답 하기전에 SecurityContextHolder 내에 SecurityContext를 삭제한다.
        • 매 요청바다 SecurityContext를 생성, SecurityContextHolder에 저장하여 Filter간 인증 정보를 확인하기 때문에 계속 저장할 필요가 없음
      • 114 Line: repo.saveContext(contextAfterChainExecution, holder.getRequest(), holder.getResponse());

      • 134 Line: public void saveContext(SecurityContext context, HttpServletRequest request, HttpServletResponse response) { ... }

  • 인증 처리된 사용자의 요청 Flow

SecurityContextPersistenceFilter_AuthenticationUser

3.6. Authentication

3.6.1. AuthenticationManager Basic

  • Authentication 인증 Flow Debug

    • UsernamePasswordAuthenticationFilter
      • 인증 필터

      • SecurityContext에 인증 객체(Authentication)을 저장

      • 89 Line: UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);

        • 요청 정보를 담은 인증 토큰으로 생성, 인증 요청을 할 수 있는 파라미터 타입으로 변환
      • 95 Line: return this.getAuthenticationManager().authenticate(authRequest);

        • 인증 토큰을 AuthenticationManager에 전달하여 인증 요청
      • AuthenticationManager (Interface)

        • 인증의 전반적인 관리

        • 현재 요청의 인증에 대해서 알맞은 Provider를 찾아 인증 처리 위임

        • AuthenticationProvider (ProviderManager)

          • UserDetails, authorities를 인증 토큰을 인증객체에 담아 반환
          • 199 Line: result = provider.authenticate(authentication);
            • 인증 요청에 맞는 AuthenticationProvider를 선택하여 인증처리를 실시함

            • AbstractUserDetailsAuthenticationProvider

              • 인증 처리 메서드가 존재하는 DaoAuthenticationProvider의 상위 클래스
              • 126 Line: public Authentication authenticate(Authentication authentication)
                • 실제 인증 처리 수행이 되는 메서드
              • 174 Line: user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);
                • 사용자를 검색

                • DaoAuthenticationProvider

                  • 103 Line: protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException { ... }

                    • 사용자 검색 요청하는 메서드
                  • InMemoryUserDetailsManager

                    • 151 Line: public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { ... }
                    • username을 통해 메모리에 저장되어 있는 사용자 정보를 요청
                    • UserDetails 타입으로 반환
  • Authentication 인증 Flow

AuthenticationManager

3.6.2. AuthenticationManager Advance

  • AuthenticationManager
    • 인증 필터로부터 인증처리를 지시 받는 인터페이스

    • 인증 처리를 할 수 있는 Provider를 찾아서 인증 처리를 위임하는 클래스

    • ParentProviderManager

    • ChildProviderManager

    • ProviderManager

      • AnonymousAuthenticationProvider
      • RememberMeAuthenticationProvider

3.7. AuthenticationProvider

  • AbstractAuthenticationProcessingFilter
    • Line 212: authResult = attemptAuthentication(request, response);
      • 구현된 필터를 통해 인증 처리
      • UsernamePasswordAuthenticationFilter
        • Line 69: public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
          • 인증 처리 로직
        • Line 95: return this.getAuthenticationManager().authenticate(authRequest);
          • 인증 토큰 전달
          • ProviderManager
            • Line 188: for (AuthenticationProvider provider : getProviders()) {
              • 프로젝트 내에 설정되어 있는 Provider 중 현재 form인증 처리를 할 수 있는 DaoAuthenticationProvider를 선택
            • Line 199 : result = provider.authenticate(authentication);
              • 현재 넘어온 인증 객체를 처리할 수 있는 DaoAuthenticationProvider에게 전달
              • AbstractUserDetailsAuthenticationProvider
                • Line 128: Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,() -> messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports", "Only UsernamePasswordAuthenticationToken is supported"));
                  • Form인증를 할 수 있도록 검증 처리
                • Line 144: user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);
                  • 유저 검색
                  • DaoAuthenticationProvider
                    • Line 103: protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
                    • 사용자 조회
                    • Line 108: UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
                    • DB에 사용자가 존재하는지 확인
                    • InMemoryUserDetailsManager
                    • Line 151: public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
                    • 전달받은 username에 대한 유저 정보를 조회
                • Line 166: additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);
                  • 패스워드 검증
                  • DaoAuthenticationProvider
                    • Line 77: protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
                • Line 197: return createSuccessAuthentication(principalToReturn, authentication, user);
                  • 인증 성공 시 인증 토큰을 만들어 인증된 객체를 AuthenticationManager에게 전달
                  • DaoAuthenticationProvider
                    • Line 128: protected Authentication createSuccessAuthentication(Object principal, Authentication authentication, UserDetails user) {
                    • 인증 객체 생성
            • 생성된 인증 객체를 인증 필터에게 전달
        • Line 95: return this.getAuthenticationManager().authenticate(authRequest);
          • 생성된 인증객체를 상위 클래스에서 성공 및 실패 처리할 수 있도록 전달
    • Line 240: successfulAuthentication(request, response, chain, authResult);
      • 인증 처리 성공시 후처리
    • Line 307: protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult)
      • SecurityContext에 Authentication(인증객체) 설정
    • Line 326: successHandler.onAuthenticationSuccess(request, response, authResult);
      • SuccessHandler에서 인증 성공시 자원 접근 가능 처리

3.8. Authorization, FilterSecurityInterceptor

  • 인증확인 > 인가여부확인(자격조건심사) > 자원접근

3.8.1. Authorization

  • 인가처리

    • 인증된 사용자의 자원접근 허가여부 확인
  • 스프링 시큐리티가 지원하는 권한 계층

    • 웹 계층(request)
      • URL 요청에 따른 메뉴 혹은 화면 단위의 레벨 보안
    • 서비스 계층(invoke)
      • 화면 단위가 아닌 메서드 같은 기능 단위의 레벨 보안
    • 도메인 계층(Access Control List, 접근 제어 목록) ( write )
      • 객체 단위의 레벨 보안

3.8.2. FilterSecurityInterceptor

  • 인가 처리를 담당하는 Filter

    • 마지막에 위치하는 필터로써 인증된 사용자에 대하여 특정 요청의 승인 / 거부 여부를 최종적으로 결정
    • 인증 객체 없이 보호자원에 접근을 시도하는 경우 AuthenticationException 발생
    • 인증 후 자원에 접근 가능한 권한이 존재하지 않은경우 AccessDeniedException 발생
    • 권한 제어 방식 중 Http 자원의 보안을 처리하는 필터
    • 권한 처리를 AccessDecisionManager 에게 맡김
  • Debug Point

    • FilterChainProxy
      • Line 334 : nextFilter.doFilter(request, response, this);
        • 필터체인 리스트 확인 포인트
        • 0 = {WebAsyncManagerIntegrationFilter@7562}
        • 1 = {SecurityContextPersistenceFilter@8944}
        • 2 = {HeaderWriterFilter@8945}
        • 3 = {CsrfFilter@8946}
        • 4 = {LogoutFilter@8947}
        • 5 = {UsernamePasswordAuthenticationFilter@8948}
        • 6 = {DefaultLoginPageGeneratingFilter@8949}
        • 7 = {DefaultLogoutPageGeneratingFilter@8950}
        • 8 = {RequestCacheAwareFilter@8951}
        • 9 = {SecurityContextHolderAwareRequestFilter@8952}
        • 10 = {AnonymousAuthenticationFilter@8953}
        • 11 = {SessionManagementFilter@8954}
        • 12 = {ExceptionTranslationFilter@8955}
        • 13 = {FilterSecurityInterceptor@8956}
  • FilterSecurityInterceptor

    • 사용자의 요청 처리 순서
      • 인증 객체의 존재여부 확인
        • Null
          • AuthenticationException
            • ExceptionTranslationFilter
        • Not Null
          • SecurityMetadataSource(resource의 자원 접근 권한 확인)
            • 권한 정보 확인
              • No(NULL인 경우 권한 심사 하지 않음)
                • 자원 접근 허용
              • Yes
                • AccessDecisionManger
                  • 심의 요청
                    • AccessDecisionVoter
                    • 승인
                    • 자원 접근 허용
                    • 거부
                    • AccessDeniedException
                    • ExceptionTranslationFilter
  • code settings

http
    /* /user resource는 USER 권한을 가진 사용자에 대해서 허용 */
    .authorizeRequests().antMatchers("/user").hasRole("USER")
    /* 그 외 요청에 대해서는 모든 사용자가 접근이 가능 */
    .anyRequest().permitAll();
  • AbstractSecurityInterceptor

    • MethodSecurityInterceptor (Interceptor)
      • 메서드 보안 인가 처리
      • AOP 기반으로 동작하는 Interceptor
    • FilterSecurityInterceptor (Filter)
    • AspectJMethodSecurityInterceptor
  • debug

  • FilterChainProxy

    • Line 334: nextFilter.doFilter(request, response, this);

      • 필터 리스트에 ExceptionTranslationFilter가 설정되어 있는 것을 확인
      • ExceptionTranslationFilter
        • Line 118: chain.doFilter(request, response);
        • FilterSecurityInterceptor
          • Line 89: FilterInvocation fi = new FilterInvocation(request, response, chain);
            • 인가 처리를 담당하는 필터
            • 부모 클래스에 전달
          • Line 123: InterceptorStatusToken token = super.beforeInvocation(fi);
            • 상위 클래스에 호출
            • AbstractSecurityInterceptor
              • Line 196: Collection attributes = this.obtainSecurityMetadataSource().getAttributes(object);
                • 유저자원("/users")에 설정된 자원접근 권한정보("USER")를 조회
              • Line 199: if (attributes == null || attributes.isEmpty()) {
                • 유저의 자원정보를 확인
                • 유저 자원에 설정된 권한 정보를 확인하여 인가처리 할 수 있도록 함
              • Line 222: if (SecurityContextHolder.getContext().getAuthentication() == null) {
                • 인증 객체가 NULL인지 아닌지 확인 (현재는 인증되지 않은 사용자로 접근: anonymousToken을 갖고 있는 사용자)
                • 인증Filter를 비정상적으로 통화하여 접근한 사용자의 경우 인증 토큰이 존재 X ?
              • Line 233: this.accessDecisionManager.decide(authenticated, object, attributes);
                • 인가 처리를 AccessDecisionManager에게 위임
                • AffirmativeBased
                  • Line 58: public void decide(Authentication authentication, Object object, Collection configAttributes) throws AccessDeniedException {
                    • 실제 인가처리를 작업
                  • Line 63: int result = voter.vote(authentication, object, configAttributes);
                    • 구현 클래스에 권한 인가 위임
                    • WebExpressionVoter
                    • Line 36: int result = voter.vote(authentication, object, configAttributes);
                    • 현 접근자원에 대한 접근 권한을 갖고 있는지 판단
          • Line 126: fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
            • 요청 자원에 대한 접근 - Line 83: if (deny > 0) { - Line 84: throw new AccessDeniedException(messages.getMessage("AbstractAccessDecisionManager.accessDenied", "Access is denied")); - 접근 권한이 없는 경우 예외 처리
              • Line 236: publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated, accessDeniedException));
                • 발생한 예외를 ExceptionTranslationFilter가 받아서 처리
                • ExceptionTranslationFilter
                  • Line 166: private void handleSpringSecurityException(HttpServletRequest request, HttpServletResponse response, FilterChain chain, RuntimeException exception)
                    • AuthenticationException 또는 AccessDeniedException 예외를 처리
                  • Line 179: else if (exception instanceof AccessDeniedException) {
                    • 인가 예외로 인한 예외 처리 로직
                  • Line 184: sendStartAuthentication(...);
                  • Line 204: protected void sendStartAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, AuthenticationException reason) throws ServletException, IOException {
                    • SecurityContext에 인증 객체 null 설정
                  • Line 212: authenticationEntryPoint.commence(request, response, reason);
                    • LoginUrlAuthenticationEntryPoint
                    • Line 133: public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
                    • Line 169: redirectStrategy.sendRedirect(request, response, redirectUrl);
                    • 로그인 페이지로 접근하여 인증 하도록 유도
    • 인증되지 않은 사용자의 접근

    • USER 권한을 가진 사용자의 접근

3.9. AccessDecisionManager, AccessDecisionVoter

  • FilterSecurityInterceptor
    • 인가 처리
    • AccessDecisionManager
      • decide

        • 권한 심사 요청
        • Authentication
        • FilterInvocation
        • ConfigAttributes
      • AccessDecisionVoter

        • 권한 심사 정보 반환
        • ACCESS_GRANTED
        • ACCESS_DENIED
        • ACCESS_ABSTAIN
      • ACCESS_DENIED

        • ExceptionTranslationFilter
          • 예외 처리

3.9.1. AccessDecisionManager Basic

  • 인증정보, 요청정보, 권한정보를 이용하여 사용자의 자원접근을 허용할 것인가를 최종 결정하는 주체

  • 여러 개의 Voter들을 가질 수 있으며 Voter들로부터 접근허용, 거부, 보류에 해당하는 각각의 값을 리턴받아 판단 및 결정

  • 최종 접근 거부 시 예외 발생

  • 접근 결정의 세 가지 유형

    • AffirmativeBased
      • 여러 개의 voter 클래스 중 하나라도 접근 허가로 결론을 내면 접근 허가로 판단
    • ConsensusBased
      • 다수표(승인 및 거부)에 의해 최종 결정을 판단
      • 동수인 경우 기본은 접근허가이나 allowIfEqualGrantedDeniedDecision을 false로 설정하는 경우 접근거부로 결정
    • UnanimousBased
      • 모든 voter가 만장일치로 접근을 승인해야하며 그렇지 않은 경우 접근을 거부

3.9.2. AccessDecisionVoter Basic

  • 판단을 심사하는 것
  • Voter가 권한 부여 과정에서 판단하는 자료
    • Authentication - 인증 정보(User)
    • FilterInvocation - 요청 정보(antMatcher("/user"))
    • ConfigAttributes - 권한 정보(hasRole("USER"))
  • 결정 방식
    • ACCESS GRANTED: 권한 허용(1)
    • ACCESS DENIED: 접근 거부(-1)
    • ACCESS ABSTAIN: 접근 보류(0)
      • voter가 해당 타입의 요청에 대해 결정을 내릴 수 없는 경우

3.9.3. AccessDecisionManager Advance

3.9.4. AccessDecisionVoter Advance

3.10 스프링 시큐리티 필터 및 아키텍처 정리

1. 익명 사용자의 인증 성공 후 루트페이지 이동 2. 동일 계정으로 접근 시 3. 기존 사용자의 접근 시