HTTP 200 OK

Memento mori & Carpe diem

Spring

Spring Security 주요 아키텍처 이해( Part 2)

sjoongh 2022. 5. 30. 16:44

인증 저장소

SecurityContextHolder, SecurityContext

SecurityContext

  • Authentication 객체가 저장되는 보관소로 필요 시 언제든지 Authentication 객체를 꺼내어 쓸 수 있도록 제공되는 클래스
  • ThreadLocal에 저장되어 아무 곳에서나 참조가 가능하도록 설계함
  • ThreadLocal : Thread마다 할당도니 고유 공간(공유X)
    • 다른 Thread로부터 안전하다
    • get, set, remove api가 있다.
    • set 한 이후 get할 대 장소의 제약이 없다 (ex : A메서드에서 set한 내용을 B메서드에서 get하는것이 가능하다)
  • 인증이 완료되면 HttpSession에 저장되어 어플리케이션 전반에 걸쳐 전역적인 참조가 가능하다
    • HttpSession은 처음 접속시에만 생성되며, 클라이언트의 웹 브라우저와 웹 서버의 연결 상태를 관리하는 객체이다.
    • session : 웹 브라우저와 웹 서버의 연결 상태(세션 유지)를 의미
    • ex 1.로그인이 성공한 후 아이디를 HttpSession 객체에 저장하면 이후 이메일 페이지에 들어갈때 객체에 저장된 아이디를 확인하고 아이디에 맞는 이메일 페이지를 열어준다.
    • ex 2. 쇼핑물에서 여러 페이지에서 물건을 장바구니에 담으면 물건들이 HttpSession 객체에 누적되어 물건 목록을 구매할 수 있음.

SecurityContextHolder

  • SecurityContext 객체를 저장하고 감싸고 있는 wrapper 클래스
  • SecurityContext 객체를 바로 저장하는 것은 아니고 3가지의 저장방식에 따라 저장한다.
    • MODE_THREADLOCAL : 스레드당 SecurityContext 객체를 할당(default)
    • MODE_INHERITABLETHREADLOCAL : 메인 스레드와 자식 스레드에 관하여 동일한 SecurityContext를 유지 -> Parent, Child thread에서 동일한 SecurityContext를 가진다
    • MODE_GLOBAL : 응용 프로그램에서 단 하나의 SecurityContext를 저장한다 -> Memory에서 단 하나의 SecurityContext를 가지고 참조
  • SecurityContextholder.clearContext(): SecurityContext 기본 정보 초기화 메서드

Authentication authentication = SecurityContextHolder.getContext().getAuthentication()

  • 어떤 메서드에서든지 참조가 가능하며, 인증객체를 어디서든 꺼내어 사용할 수 있다.

Flow

  1. Login을 사용자가 시도
  2. Server가 요청을 받아서 Thread를 생성(ThreadLocal 할당)
    • process : 실행 중인 하나의 어플리케이션(운영체제로부터 할당 받는 작업 단위)
    • Thread : 한 프로세스 내에서 동작 되는 여러 실행 흐름(프로세스가 할당 받은 자원을 이용하는 실행의 단위, 추가 가능)
    • 즉 자바의 JVM위에서 하나의 프로세스가 돌아가고, 프로세스 안에서 main문 하나의 스레드가 실행되는 것이다.
    • 스레드 & 멀티스레드 참고
    • ThreadLocal : 자바의 class이며 오직 한 Thread에 의해서 읽고 쓰여질 수 있는 변수이다.
  3. thread가 인증 처리 시도 -> 인증 객체(Authentication) 생성
  4. (인증 실패) SecurityContextHolder.clearContext() 인증객체 초기화
  5. (인증 성공) SecurityContextHolder안의 SecurityContext에 인증객체(Authentication) 저장 -> ThreadLocal이 SecurityContextHloder를 담고 있는 것이다.
  6. SecurityContext에서 최종적으로 HttpSession에 저장된다 -> SPRING_SECURITY_CONTEXT라는 이름으로 저장된다.

SecurityContextHolder의 SecurityContext저장 전략별 차이점 살펴보기

  1. 기본전략::MODE_THREADLOCAL
  2. MODE_INHERITABLETHREADLOCAL 전략
  • 흔히 위의 두 가지 방식을 사용한다. 멀티스레드 일 경우는 MODE_INHERITABLETHREADLOCAL를 사용하면 좋을 것 같고 멀티스레드를 구현할 필요성이 없는 경우에는 기본전략(MODE_THREADLOCAL)으로 구현하는게 편리할 것 같다.(주로 기본전략 사용할듯..)

인증 저장소 필터

  • SecurityContextPersistenceFilter

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

  • 익명 사용자
    • 새로운 SecurityContext 객체를 생성하여 SecurityContextHolder에 저장한다.
    • AnonymouseAuthenticationFilter에서 AnonymousAuthenticationToken 객체를 SecurityContext에 저장한다.
  • 인증 시
    • 새로운 SecurityContext객체를 생성하여 SecurityContextHolder에 저장한다.
    • AnonymouseAuthenticationFilter에서 AnonymousAuthenticationToken 객체를 SecurityContext에 저장한다.
  • 인증 후
    • Session에서 SecurityContext를 꺼내어 SecurityContextHolder에서 저장한다.
    • SecurityContext안에 Authentication 객체가 존재하면 계속 인증을 유지한다.
  • 최종 응답 시 공통
    • SecurityContextHolder안의 SecurityContext객체에서 보관하던 인증정보를 반드시 초기화 해줘야 한다.
    • SecurityContextHolder.clearContext() 메서드를 호출해 인증 정보를 초기화 한다.

Flow

Flow2

  1. 사용자가 Request 요청
  2. SecurityContextPersistenceFilter는 매 번 요청마다 수행된다.
  3. SecurityContextPersistenceFilter내부적으로 HttpSecurityContextRepository가 로직을 수행
    • HttpSecurityContextRepository: SecurityContext객체를 생성, 조회 하는 역할을 하는 클래스
    • 인증 전
      • 새로운 컨텍스트 생성(SecurityContextHolder) -> 이 때는 SecurityContext 객체는 null이다.
      • 그 다음 필터로 이동한다(chain.doFilter), 필터체인으로 연결하여 줍니다. 체인의 경우 순서를 지정할 수 있습니다. 체인의 가장 마지막에는 클라이언트가 요청한 최종 자원이 위치합니다.
      • 인증 필터(AuthFilter)가 인증을 처리한다.
      • 인증이 완료되면 인증객체(Authenticaion)생성 후 SecurityContext 객체 안에 저장된다.
      • 다음 필터 수행(chain.doFilter)
      • Client에게 응답하는 시점에서 Session에 SecurityContext저장 -> SecurityContextPerstenceFilter가 하는 것이다.
      • SecurityContext 제거(Clear())
      • 응답(Response)
    • 인증 후
      • Session에서 SecurityContext가 있는지 확인 -> 인증이 된 이후이기에 존재한다.
      • SecurityContext를 꺼내어 SecurityContextHolder에 집어넣는다.
      • 다음 필터 수행(chain.doFilter)

Flow - Simple

SimpleFlow

  • SecurityContextPersistenceFIlter는 인증 전(좌측) 인증 후(우측)으로 나뉘는데, 인증 후에는 Session에서 SecurityContext를 꺼내 SecurityContextHolder에 집어넣은 뒤 다음 필터들을 수행한다. 하지만, 아직 인증 전이라면 꺼낼 SecurityContext는 없기 때문에 SecurityContextHolder를 생성한 뒤 인증필터(AuthFilter)를 수행 후 생성된 인증객체(Authentication)을 SecurityContext에 담은 뒤 다음 로직을 수행한다.

그리고 마지막으로 Client에게 응답(Response)하기 전 Session에 SecurityContext를 담은 뒤 SecurityContext는 초기화 해준 다음 응답을 합니다.


인증 흐름 이해

  • Authentication Flow

Authentication Flow

  1. Client에서 로그인 요청
  2. UsernamePasswordAuthenticationFIlter에서 ID + PASSWORD를 담은 인증 객체(Authentication)를 생성한다.
  3. AuthenticationManager에게 인증객체를 넘기며 인증처리를 위임한다.
    • 내부에 한 개 이상의 Provider를 담은 List를 가지고 있고 그 List에서 적절한 Provider를 찾는다.
  4. 인증관리자(AuthenticationManager)는 적절한 Provider(AuthenticationProvider)에게 인증처리를 위임한다.
  5. 해당 Provider는 input 정보(id, password)를 가지고 실제 인증 처리 역할을 한다.
    • loadUserByUsername(username)메서드를 호출해서 유저객체를 요청한다.
    • UserDetailsService 인터페이스에게 loadUserByUsername(username) 요청
      • Repository에 findById() 메서드로 유저 객체 조회를 한다.
      • 만약, 해당 유저객체가 존재하지 않으면 UsernameNotFoundException 이 발생하고 UsernamePasswordAuthenticationFIlter에서 예외를 처리한다. -> FailHandler()에서 후속처리
      • 존재한다면 UserDetails 타입으로 반환된다.(Member 객체도 UserDetails 객체로 변환되어 반환)
  6. 인증관리자(AuthenticationManager)는 이제 Password 검증을 시작한다.
    • 인증객체의 Password와 반환받은 UserDetails의 Password를 비교한다.
      • 일치하지 않을 경우 BadCredentialException 발생 후 인증 실패
    • 성공한 인증객체(UserDetals와 authorities를 담은 인증 후 토큰 객체 Authentication)를 UsernamePasswordAuthenticationFilter에 전다한다.
  7. SecurityContext에 저장한다.
  8. 이후 전역적으로 SecurityContextHolder에서 인증객체를 사용가능하게 된다.

인증 관리자

  • AuthenticationManager

인증 관리자(AuthenticationManager)는 필터로부터 인증처리를 지시받으면 가지고 있는 인증 처리자(AuthenticationProvider)들 중에서 현재 인증처리를 할 수 있는 Provider에게 인증처리를 위임하여 인증처리 수행 후 인증 성공을 한다면 반환받은 인증객체를 필터로 전달해준다.

만약, 적절한 Provider를 못 찾는다면 자신의 부모 객체에 저장된 ProviderManager도 검색하여 해당 인증을 처리할 수 있는 Provider가 있으면 인증처리를 위임하여 반환한다.

manager

  • AuthenticationManager는 인터페이스, ProviderManager는 구현체
  • ProviderManager는 AuthenticationProvider 목록 중 인증처리 요건에 맞는 적절한 Provider를 찾아 인증처리를 위임하는 클래스
  • 적절한 AuthenticationProvider를 찾지 못하면 부모 ProviderManager에서 AuthenticationProvider 탐색 가능

인증 처리자

  • AuthenticationProvider

provider

  • AuthenticationProvider 는 인터페이스다. 두 개의 메서드가 제공된다.
    • authenticate(authentication) : 실제적인 인증처리를 위한 검증 메서드
    • supports(authentication): 인증처리가 가능한 Provider인지 검사하는 메서드
  • 두 개의 메서드는 사용자가 입력한 아이디와 패스워드가 들어간 authentication객체를 가지고 로직을 수행한다.
  1. 아이디 검증
    • UserDetailsService 인터페이스에서 인증을 요구하는 사용자 정보를 조회한다.
      • 존재할 경우 UserDetails 타입으로 반환한다.
      • 존재하지 않을 경우 UserNotFoundException 발생
  2. 패스워드 검증
    • 반환된 UserDetails에 저장된 password와 로그인시 입력한 패스워드 (authentication.password)가 일치하는지 비교한다.
      • 일치하지 않을 경우 BadCredentialException 발생
    • 일반적으로 패스워드를 저장할 때 Encoder를 이용해 암호화 하여 저장하기 때문에 해당 클래스(PasswordEncoder)를 이용해 두 암호를 비교한다.
  3. 추가 검증
    • 추가적으로 사용자가 정의한 검증 조건 검증
    • autheticate(authentication) 에서 검증이 모두 성공하면 최종적으로 인증객체(Authentication(user,authorities))를 생성하여 AuthenticationManager에 전달한다.

인가 개념 및 필터 이해

  • Authorization, FilterSecurityInterceptor

Authorization - 인가 처리

authorization

  • 인증 된 사용자가 특정 자원에 접근하고자 할 때 접근 할 자격이 되는지 증명하는 것을 인가(Authorization)이라 한다.
  • 사용자가 특정 자원에 접근하고자 요청(Request)을 하면 그 사용자가 인증을 받았는지 확인한다.
  • 인증을 받은 사용자라면 해당 사용자의 자격(권한)이 해당 자원에 접근할 자격이 되는지 확인한다.
    • 위 그림에서 사용자는 Manager이기 때문에 인증은 허가된 상태이다.
    • 인가 처리 부분에서 사용자는 Manager이기 때문에 User Section, Manager Section까지는 접근이 가능하다.
    • Admin Section의 Resources는 권한 부족으로 접근이 허용되지 않는다.

스프링 시큐리티가 지원하는 권한 계층

1. 웹 계층

  • URL 요청에 따른 메뉴 혹은 화면단위의 레벨 보안
    • /user 경로로 자원 접근을 할 때 그 자원에 설정된 권한(ROLE_USER)과 사용자가 가진 권한을 서로 심사해서 결정하는 계층

2. 서비스 계층

  • 화면 단위가 아닌 메소드 같은 기능 단위의 레벨 보안
    • user()라는 메소드에 접근하고자 할 때 해당 메소드에 설정된 권한과 사용자가 가진 권한을 서로 심사해서 결정하는 계층

3. 도메인 계층(Access Control List, 접근 제어 목록)

  • 객체 단위의 레벨 보안
    • 객체(user)를 핸들링 하고자 할 때 도메인에 설정된 권한과 사용자가 가진 권한을 서로 심사해서 결정하는 계층

FilterSecurityInterceptor - 인가 처리 담당 필터

  • 마지막에 위치한 필터로써 인증된 사용자에 대해 특정 요청의 승인 및 거부를 최종적으로 결정
  • 인증객체 없이 보호자원에 접근을 시도하면 AuthenticationException 발생
  • 인증 후 자원에 접근 가능한 권한이 존재하지 않을 경우 AccessDeniedException 을 발생
  • 권한 제어 방식 중 HTTP 자원의 보안을 처리하는 필터 -> URL방식으로 접근할 경우 동작한다
  • 권한 처리를 AccessDecisionManager에게 맡긴다

FlOW

flow

  1. 사용자가 자원 접근(Request)
  2. FilterSecurityInterceptor에서 요청을 받아서 인증여부를 확인한다.
    • 인증객체를 가지고 있는지 확인한다
    • 인증객체가 없으면(null) AuthenticationException 발생
    • ExceptionTranslationFilter에서 해당 예외를 받아서 다시 로그인 페이지로 이동하던가 후처리를 해준다.
  3. 인증객체가 있을 경우 SecurityMetadataSource는 자원에 접근하기 위해 설정된 권한정보를 조회해서 전달해준다.
    • 권한 정보를 조회한다
    • 권한 정보가 없으면(null) 권한 심사를 하지 않고 자원 접근을 허용한다.
  4. 권한 정보가 있을 경우 AccessDecisionManager 에게 권한 정보를 전달하여 위임한다.
    • AccessDecisionManager는 최종 심의 결정자다.
  5. AccessDecisionManager가 내부적으로 AccessDecisionVoter(심의자)를 통해서 심의 요청을 한다.
  6. 반환된 승인/거부 결과를 가지고 사용자가 해당 자원에 접근이 가능한지 판단한다.
    • 접근이 거부되었을 경우 AccessDeniedException이 발생한다
    • ExceptionTranslationFilter에서 해당 예외를 받아서 다시 로그인 페이지로 이동하던가 후처리를 해준다.
  7. 접근이 승인되었을 경우 자원 접근이 허용된다.

인가 결정 심의자

  • AccessDecisionManager, AccessDecisionVoter

AccessDecisionManager::인터페이스

  • 인증, 요청, 권한 정보를 이용해서 사용자의 자원접근을 허용/거부 여부를 최종 결정하는 주체다.
  • 여러 개의 Voter들을 가질 수 있고, Voter들로부터 접근허용, 거부, 보류에 해당하는 각각의 값을 리턴받아 판단&결정한다,
  • 최종 접근 거부시 예외 발생

AccessDecisionVoter

  • 판단을 심사하는 것(위원개념)
  • 각각의 Voter에서 사용자의 요청마다 해당 자원에 접근할 권한이 있는지 판단 후 AccessDecisionManager에게 반환하는 역할

Voter가 권한 부여 과정에서 판단하는 자료 - 아래 정보를 전달받아 판단

  • Authenticaion : 인증정보(user)
  • FilterInvocator : 요청정보(antMatcher("/user"))
  • ConfigAttributes : 권한 정보(hasRole("USER"))

결정 방식 - AccessDecisionManager 은 반환 받은 결정 방식을 사용해서 후처리를 한다.

  • ACCESS_GRANTED : 접근 허용(1)
  • ACCESS_DENIED : 접근 거부(-1)
  • ACCESS_ABSTAIN : 접근 보류(0)
    • Voter가 해당 타입의 요청에 대해 결정을 내릴 수 없는 경우

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

security

  • 사용자가 자원요청을 할 때 이루어지는 SpringSecurity의 보안과정 전체 Flow이다.