Spring Security의 웹 요청 처리 흐름

InSeok·2023년 1월 31일
0

TIL

목록 보기
40/51

**Spring Security의 웹 요청 처리 흐름**

보안이 적용된 웹 요청의 일반적인 처리 흐름

  • 사용자의 웹 요청이 Controller 같은 엔드포인트를 거쳐 접근하려는 리소스에 도달하기 전에 인증 관리자나 접근 결정 관리자 같은 컴퍼넌트가 중간에 웹 요청을 가로채 사용자의 크리덴셜과 접근 권한을 검증

사용자의 크리덴셜(Credential)이란
해당 사용자를 증명하기 위한 구체적인 수단을 의미합니다. 일반적으로는 사용자의 패스워드가 크리덴셜에 해당

서블릿 필터 체인 구성도

  • 서블릿 필터는 각각의 필터들이 doFilter()라는 메서드를 구현해야 하며, doFilter()
    메서드 호출을 통해 필터 체인을 형성한다.
  • 웹 요청이 Controller 같은 엔드포인트를 거쳐 접근하려는 리소스에 도달하기 전에 인증 관리자나 접근 결정 관리자 같은 컴퍼넌트가 중간에 웹 요청을 가로채 사용자의 크리덴셜과 접근 권한을 검증한다.

서블릿 필터(Servlet Filter)

  • 엔드포인트에 요청이 도달하기 전에 중간에서 요청을 가로챈 후 어떤 처리를 할 수 있는 포인트
  • 요청(request)을 가로채어 어떤 처리(전처리)를 할 수 있으며, 또한 엔드포인트에서 요청 처리가 끝난 후 전달되는 응답(reponse)을 클라이언트에게 전달하기 전에 어떤 처리(후처리)를 할 수 있다.
  • 서블릿 필터는 하나 이상의 필터들을 연결해 필터 체인(Filter Chain)을 구성할 수있다.\
  • 서블릿 필터는 각각의 필터들이 doFilter()라는 메서드를 구현해야 하며, doFilter()
    메서드 호출을 통해 필터 체인을 형성

서블릿 필터와 연결되는 Spring Security만의 필터를 ApplicationContext에 Bean으로 등록한 후에 이 Bean들을 이용해서 보안과 관련된 여러가지 작업들을 처리한다.

DelegatingFilterProxyFilterChainProxy클래스는 Filter 인터페이스를 구현하기 때문에 서블릿 필터로써의 역할을 합니다.

DelegatingFilterProxy

  • Bean으로 등록된 Spring Security의 필터를 사용하는 시작점
  • 서블릿 컨테이너 영역의 필터와 ApplicationContext에 Bean으로 등록된 필터들을 연결해주는 브릿지 역할

FilterChainProxy

  • Spring Security의 Filter를 사용하기 위한 진입점
  • Spring Security의 Filter Chain은 보안을 위한 작업을 처리하는 필터의 모음이다.
  • Spring Seucrity에서 제공하는 보안 필터들이 필요한 작업을 수행
  • Spring Security의 Filter Chain은 URL 별로 여러개 등록할 수 있으며, Filter Chain이 있을 때 어떤 Filter Chain을 사용할지는 FilterChainProxy가 결정하며, 가장 먼저 매칭된 Filter Chain을 실행
  • Servlet FilterChain은 요청 URI path를 기반으로 HttpServletRequest를 처리
  • Filter Chain에서 Filter의 순서는 매우 중요
    • Filter에 @Order애너테이션을 추가하거나 Orderd 인터페이스를 구현해서 순서를 지정
    • FilterRegistrationBean을 이용해 Filter의 순서를 지정가능
    • /api/** 패턴의 Filter Chain이 있고, /api/message URL 요청이 전송하는 경우
      • /api/** 패턴과 제일 먼저 매칭되므로, 디폴트 패턴인 /**도 일치하지만 가장 먼저 매칭되는 /api/** 패턴과 일치하는 Filter Chain만 실행합니다.

요청 처리 흐름

Filter 인터페이스 기본구조

public class FirstFilter implements Filter {
     // (1) 초기화 작업
     public void init(FilterConfig filterConfig) throws ServletException {

     }

     // (2)
     public void doFilter(ServletRequest request,
                          ServletResponse response,
                          FilterChain chain)
                          throws IOException, ServletException {
        // (2-1) 이 곳에서 request(ServletRequest)를 이용해 다음 Filter로 넘어가기 전처리 작업을 수행한다.

        // 해당 Filter가 처리하는 실질적인 로직을 구현
        chain.doFilter(request, response);

        // (2-3) 이 곳에서 response(ServletResponse)를 이용해 response에 대한 후처리 작업을 할 수 있다.
     }

     // (3)
     public void destroy() {
        // (5) Filter가 사용한 자원을 반납하는 처리
     }
  }

Servlet Filter는 FilterRegistrationBean의 생성자로 Filter 인터페이스의 구현 객체를 넘겨주는 형태로 등록가능

  • FilterRegistrationBean의setOrder()메서드로 Filter 순서를 지정할 수 있다. 적은숫자일수록 먼저 실행

**DelegatingPasswordEncoder**

  • Spring Security에서 지원하는 PasswordEncoder구현 객체를 생성해주는 컴포넌트
  • 애플리케이션에서 사용할 PasswordEncoder를 결정하고, 결정된 PasswordEncoder로 사용자가 입력한 패스워드를 단방향으로 암호화
  • 지정하지 않는다면 Spring Security에서 권장하는 최신 암호화 알고리즘을 사용
  • 레거시 방식의 암호화 알고리즘으로 암호화 된 패스워드의 검증을 지원
  • 언제든지 암호화 방식을 변경가능 ,but 기존에 암호화 되어 저장된 패스워드에 대한 마이그레이션 작업이 진행필수
  • PasswordEncoder passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();

**Custom DelegatingPasswordEncoder**

String idForEncode = "bcrypt";
Map encoders = new HashMap<>();
encoders.put(idForEncode, new BCryptPasswordEncoder());
encoders.put("noop", NoOpPasswordEncoder.getInstance());
encoders.put("pbkdf2", new Pbkdf2PasswordEncoder());
encoders.put("scrypt", new SCryptPasswordEncoder());
encoders.put("sha256", new StandardPasswordEncoder());

PasswordEncoder passwordEncoder = new DelegatingPasswordEncoder(idForEncode, encoders);

**암호화 된 Password Format**

  • Spring Security 5에서는 패스워드를 암호화 할 때, 암호화 알고리즘 유형을 prefix로 추가
  • {id}encodedPassword
  • 다이제스트(Digest) : 원본 메시지를 암호화한 메시지
  • 해시(Hash) 알고리즘
    • 단방향 암호화를 위한 핵심 알고리즘
    • 한번 암호화 되면 복호화 되기 어려움
  • SHA(Secure Hash Algorithm)
    • 해시된 문자열을 만들어내기 위해 비트 회전 연산이 추가된 방식(해시된 문자열의 비트 값을 회전하면서 반복적으로 해시 처리)
  • Rainbow Attack
    • 사용자가 패스워드로 사용할만한 문자열들을 미리 목록(Rainbow Table)으로 만들어 놓고, 이 목록에 있는 문자열을 동일한 알고리즘으로 암호화 한 후, 탈취한 암호화 된 문자열과 서로 비교하는 작업을 통해 패스워드의 원본 문자열을 알 수 있게된다.
  • 키 스트레칭
    • 해시된 다이제스트를 또 해시하고, 또 해시된 다이제스트를 반복적으로 해시하는 것
  • 솔트(Salt)
    • 패스워드로 입력하는 원본 메시지에 임의의 어떤 문자열을 추가해서 해시 처리하는 것
  • Work Factor
    • 공격자가 해시 된 메시지를 알아내는데 더 느리게 더 비용이 많이 들게 해주는 특정 요소
  • scrypt
    • 다이제스트 생성 시, 메모리 오버헤드를 갖도록 설계

로그인인증 요청 전송시 인증처리 흐름

  • 사용자의 로그인 요청을 처리하는 Spring Security Filter UsernamePasswordAuthenticationFilter 이다.
  • UsernamePasswordAuthenticationTokenAuthentication인터페이스를 구현한 구현 클래스이며, 여기서의 Authentication아직 인증이 되지 않은 Authentication 이다.
  • AuthenticationManager는 인증 처리를 총괄하는 매니저 역할을 하는 인터페이스이고, AuthenticationManager를 구현한 구현 클래스가 ProviderManager
  • UserDetails는 사용자의 자격을 증명해주는 크리덴셜(Credential)즉, 데이터베이스 등의 저장소에 저장된 사용자의 Username과 Password, 그리고 사용자의 권한 정보를 포함하고 있는 컴포넌트
  • UserDetailsService : UserDetails load하여 제공
  • UsernamePasswordAuthenticationFilter가 생성하는 Authentication인증을 위해 필요한 사용자의 로그인 정보를 가지고 있지만, AuthenticationProvider 가 생성한 Authentication인증에 성공한 사용자의 정보(Principal, Credential, GrantedAuthorities)를 가지고 있다.
  • 인증된 Authentication을 전달 받은 UsernamePasswordAuthenticationFilterSecurityContextHolder를 이용해 SecurityContext인증된 Authentication을 저장
  • SecurityContext는 다시 HttpSession에 저장되어 사용자의 인증 상태를 유지

인증 컴포넌트

UsernamePasswordAuthenticationFilter

  • AbstractAuthenticationProcessingFilter를 상속
  • doFilter()메서드가 없음 → 상위클래스가doFilter() 메서드 포함
  • AntPathRequestMatcher : 클라이언트의 URL에 매치되는 매처
    • 상위클래스에 전달되어 Filter가 구체적인 작업을 수행할지 특별한 작업 없이 다른 Filter를 호출할지 결정하는데 사용
  • attemptAuthentication()메서드 :클라이언트에서 전달한 username과 password 정보를 이용해 인증을 시도하는 메서드
    • 상위클래스의 doFilter() 메서드에서 호출됨
    • Filter에서 어떤 처리를 하는 시작점은doFilter() 이다.
    • 클라이언트에서 전달한 username과 password 정보를 이용해 UsernamePasswordAuthenticationToken을 생성
    • AuthenticationManagerauthenticate() 메서드를 호출해 인증 처리를 위임

**AbstractAuthenticationProcessingFilter**

  • 실질적인 인증 시도는 하위 클래스에 맡기고, 인증에 성공하면 인증된 사용자의 정보를 SecurityContext에 저장하는 역할
  • 하위클래스의 AntPathRequestMatcher("/login","POST")의 파라미터인 URL과 HTTP Method가 매칭 조건이 되어 들어오는 요청이 인증 처리를 해야 되는지 여부를 결정한다.
  • 그리고나서 하위 클래스에 인증을 시도해 줄 것을 요청
  • 인증에 성공한다면, SecurityContextHolder를 통해 사용자의 인증 정보를 SecurityContext에 저장한 뒤, SecurityContext를 HttpSession에 저장
  • 실패할 경우, SeucurityContext를 초기화하고, AuthenticationFailureHandler를 호출

**UsernamePasswordAuthenticationToken**

  • Spring Security에서 Username/Password로 인증을 수행하기 위해 필요한 토큰
  • AbstractAuthenticationToken 추상 클래스를 상속하는 확장 클래스이자 Authentication 인터페이스의 메서드 일부를 구현하는 구현 클래스
  • 인증 성공 후 인증에 성공한 사용자의 인증 정보가 UsernamePasswordAuthenticationToken에 포함되어 Authentication 객체 형태로 SecurityContext에 저장된다.
  • unauthenticated()메서드는 인증에 필요한 용도의 UsernamePasswordAuthenticationToken 객체를 생성하고,
  • authenticated()메서드는 인증에 성공한 이후 SecurityContext에 저장될 UsernamePasswordAuthenticationToken 객체를 생성

**Authentication**

  • 생성된 토큰을 리턴 받거나 SecurityContext에 저장될 경우에 Authentication 형태로 리턴 받거나 저장된다.
  • Principal
    • 사용자를 식별하는 고유 정보
    • Username/ UserDetails
  • Credentials
    • 사용자 인증에 필요한 Password
    • 인증이 이루어지고 난 직 후, ProviderManager가 해당 Credentials를 삭제
  • Authorities
    • AuthenticationProvider에 의해 부여된 사용자의 접근 권한 목록
    • 일반적으로 GrantedAuthority인터페이스의 구현 클래스는 SimpleGrantedAuthority 이다.
  • **AuthenticationManager**
    • 인증 처리를 총괄하는 매니저 역할
  • **ProviderManager**
    • **AuthenticationManager 구현 클래스**
    • AuthenticationProvider를 관리하고, AuthenticationProvider에게 인증 처리를 위임하는 역할
    • List<AuthenticationProvider>객체를 DI 받는다
    • for문으로 적절한 AuthenticationProvider를 찾는다.
    • 해당 AuthenticationProvider에게 인증 처리를 위임
    • 인증이 정상적으로 처리되었다면 인증에 사용된 Credentials를 제거
  • **AuthenticationProvider**
    • AuthenticationManager로부터 인증 처리를 위임 받아 실질적인 인증 수행을 담당하는 컴포넌트
    • Username/Password 기반의 인증 처리는 DaoAuthenticationProvider
      가 담당
    • AuthenticationProvider 인터페이스의 구현 클래스는AbstractUserDetailsAuthenticationProvider이고, DaoAuthenticationProvider는 AbstractUserDetailsAuthenticationProvider를 상속한 확장 클래스
    • AbstractUserDetailsAuthenticationProvider추상 클래스의 authenticate()
      메서드에서부터 실질적인 인증 처리가 시작된다
    • DaoAuthenticationProviderUserDetailsService로부터 전달 받은 UserDetails
      를 이용해 인증을 처리
    • UserDetails는 사용자를 인증하는데 사용될 뿐만 아니라 인증에 성공할 경우, 인증된 Authentication 객체를 생성하는데 사용
    • additionalAuthenticationChecks() 메서드에서 PasswordEncoder를 이용해 사용자의 패스워드를 검증
  • DaoAuthenticationProviderAbstractUserDetailsAuthenticationProvider 가 호출되는 순서
    1. AbstractUserDetailsAuthenticationProvider의 authenticated() 메서드 호출
    2. DaoAuthenticationProvider의 retrieveUser() 메서드 호출
    3. DaoAuthenticationProvider의 additionalAuthenticationChecks() 메서드 호출
    4. DaoAuthenticationProvider 의 createSuccessAuthentication() 메서드 호출
    5. AbstractUserDetailsAuthenticationProvider 의 createSuccessAuthentication() 메서드 호출
    6. 인증된 Authentication을 ProviderManager에게 리턴
  • **UserDetails**
    • 사용자의 자격을 증명해주는 크리덴셜(Credential)
    • 데이터베이스 등의 저장소에 저장된 사용자의 Username과 Password 그리고 사용자의 권한 정보를 포함하는 컴포넌트
  • **UserDetailsService**
    • UserDetails를 로드(load)하는 핵심 인터페이스

  • SecurityContext
    • 인증된 Authentication 객체를 저장하는 컴포넌트
    • SecurityContext에 값이 채워져 있다면 인증된 사용자로 간주
  • SecurityContextHolder
    • SecurityContext를 관리하는 역할

    • SecurityContextHolder를 통해 인증된 Authentication을 SecurityContext에 설정할 수 있고,
      인증된 Authentication 객체에 접근가능

    • SecurityContextHolder 기본 전략은 ThreadLocalSecurityContextHolderStrategy

      • 현재 실행 쓰레드에 SecurityContext를 연결하기 위해 ThreadLocal을 사용하는 전략
    • getContext()메서드를 통해 현재 실행 쓰레드에서 SecurityContext를 얻을 수 있다.

    • setContext() 메서드는 현재 실행 쓰레드에 SecurityContext를 연결

      ThreadLocal : 쓰레드 간에 공유되지 않는 쓰레드 고유의 로컬 변수 같은 영역

Spring Security의 권한 부여 처리 흐름

AuthorizationFilter

  • URL을 통해 사용자의 액세스를 제한하는 권한 부여 Filter
  • SecurityContextHolder로 부터 Authentication을 획득
  • Authentication과 HttpServletRequest를 AuthorizationManager에게 전달
  • AuthorizationFilter 객체가 생성될 때, AuthorizationManager를 DI 받는다.
  • DI 받은 AuthorizationManager의 check() 메서드를 호출해 적절한 권한 부여 여부를 체크

AuthorizationManager

  • 권한 부여 처리를 총괄하는 매니저 역할

RequestMatcherDelegatingAuthorizationManager

  • AuthorizationManager를 구현하는 구현체
  • 직접 권한 부여 처리를 하는 것이 아니라 RequestMatcher를 통해 매치되는 AuthorizationManager구현 클래스에게 위임만 한다
  • 해당 AuthorizationManager구현 클래스가 사용자의 권한을 체크
  • 적절한 권한이라면 다음 요청 프로세스를 지속 이행
  • 적절한 권한이 아니라면 AccessDeniedException이 throw되고 ExceptionTranslationFilter
    AccessDeniedException을 처리
  • check() 메서드의 내부에서 루프를 돌면서 RequestMatcherEntry 정보를 얻은 후RequestMatcher 객체를 얻는다.
  • MatchResult.isMatch()가 true이면 AuthorizationManager 객체를 얻은 뒤, 사용자의 권한을 체크
  • RequestMatcher는 SecurityConfiguration에서 .antMatchers("/orders/**").hasRole("ADMIN") 와 같은 메서드 체인 정보를 기반으로 생성된다

Spring Security에서 지원하는 표현식(Spring EL)

표현식설명
hasRole(Stirng role)- 현재 보안 주체(principal)가 지정된 역할을 갖고 있는지 여부를 확인하고 가지고 있다면 true를 리턴한다.- hasRole(’admin’)처럼 파라미터로 넘긴 role이 ROLE_ 로 시작하지 않으면 기본적으로 추가한다.(DefaultWebSecurityExpressionHandler의 defaultRolePrefix를 수정하면 커스텀할 수 있다.)
hasAnyRole(String… roles)- 현재 보안 주체가 지정한 역할 중 1개라도 가지고 있으면 true를 리턴한다.(문자열 리스트를 콤마로 구분해서 전달한다.)- ex) hasAnyRole(’admin’, ‘user’)
hasAuthority(String authority)- 현재 보안 주체가 지정한 권한을 갖고 있는지 여부를 확인하고 가지고 있다면 true를 리턴한다.- ex) hasAuthority(’read’)
hasAnyAuthority(String… authorities)- 현재 보안 주체가 지정한 권한 중 하나라도 있으면 true를 리턴한다.- ex) hasAnyAuthority(’read’, ‘write’)
principal- 현재 사용자를 나타내는 principal 객체에 직접 접근할 수 있다.
authentication- SecurityContext로 조회할 수 있는 현재 Authentication 객체에 직접 접근할 수 있다.
permitAll- 항상 true로 평가한다.
denyAll- 항상 false로 평가한다.
isAnonymous()- 현재 보안 주체가 익명 사용자면 true를 리턴한다.
isRememberMe()- 현재 보안 주체가 remember-me 사용자면 true를 리턴한다.
isAuthenticated()- 사용자가 익명이 아닌 경우 true를 리턴한다.
isFullyAuthenticated()- 사용자가 익명 사용자나 remember-me 사용자가 아니면 true를 리턴한다.
hasPermission(Object target, Object permission)- 사용자가 target에 해당 permission 권한이 있으면 true를 리턴한다.ex) hasPermission(domainObject, ‘read’)
hasPermission(Object targetId, String targetType, Object permission)- 사용자가 target에 해당 permission 권한이 있으면 true를 리턴한다.ex) hasPermission(1, ‘com.example.domain.Message’, ‘read’)
profile
백엔드 개발자

0개의 댓글