본문 바로가기
개발/Spring

Spring security Architecture, Filter

by BellOne4222 2024. 2. 29.

내부 구조

SecurityContextHolder -> SecurityContext -> Authentication -> Principal & GrantAuthority

 

  • SecurityContextHolder 
    • SecurityContext를 제공하는 static 메소드(getContext)를 지원
  • SecurityContext 
    • 접근 주체와 인증에 대한 정보(Authentication)를 담고 있는 Context
SecurityContext securityContext = SecurityContextHolder.getContext();

 

Context로 Authentication 포함

  • principal에는 유저 정보 저장
  • Authentication
    • Principal, GrantAuthority 제공
    • 인증이 이루어지면 해당 Authentication을 저장
  • Principal 
    • 유저에 해당하는 정보
    • Principal로 UserDetail 반환
  • GrantAuthority
    • ROLE_ADMIN, ROLE_USER 등 Principal이 가지고 있는 권한
    • prefix로 ROLE_ 사용
    • 인증 이후에 인가를 할 때 사용
    • 권한이 여러개 일 수 있기 때문에 Collection<GrantedAuthority> 형태로 제공

 

ThreadLocal

  • 요청 1개에 Thread 1개 생성
  • ThreadLocal을 사용하면 Thread마다 고유한 공간을 만들 수 있고 그곳에 SecurityContext를 저장 할 수 있다.
  • SecurityContext 공유 전략
    • MODE_THREADLOCAL
      • ThreadLocalSecurityContextHolderStrategy를 사용
      • ThreadLocal을 사용하여 같은 Thread안에서 SecurityContext를 공유
      • 기본 설정 모드
    • MODE_INHERITABLETHREADLOCAL
      • InheritableThreadLocalSecurityContextHolderStrategy를 사용
      • InheritableThreadLocal을 사용하여 자식 Thread까지도 SecurityContext를 공유
    • MODE_GLOBAL
      • GlobalSecurityContextHolderStrategy를 사용
      • Global로 설정되어 애플리케이션 전체에서 SecurityContext를 공유
  • ThreadLocal은 Thread마다 고유한 영역을 가지고 있는 곳에 저장된 변수로 각각의 Thread안에서 유효한 변수
  • 일반적인 서버의 경우에는 외부로부터 요청이 오면 그 요청마다 Thread 1개가 할당
  • ThreadLocal로 SecurityContext를 관리하게 되면 SecurityContext는 요청마다 독립적으로 관리될 수 있다.

 

PasswordEncoder

  • Password 관리
    • 회원가입할 때 Password를 입력받으면 그 값을 암호화해서 저장해야한다.
    • 로그인할 때 입력받은 Password와 회원가입할 때의 Password를 비교할 수 있어야한다.
  • 해시 함수 알고리즘 방식
    • 암호화는 비교적 쉽지만 복호화가 거의 불가능한 방식의 알고리즘
    • 회원가입할 때 password를 해시함수로 암호화해서 저장
    • 로그인할 때 password가 들어오면 같은 해시함수로 암호화
    • 저장된 값을 불러와서 2번의 암호화된 값과 비교
    • 동일하면 같은 암호로 인지

로그인시 BcryptPasswordEncoder를 통해 암호화

 

PasswordEncorder 종류

  • 어떤 암호는 bcrypt로 암호화되고 다른 암호는 sha256 되었다고 하더라도 DelegatingPasswordEncoder는 둘다 지원할 수 있다.
  • BcryptPasswordEncoder
    • Bcrypt 해시 함수를 사용한 PasswordEncoder
    • Password를 무작위로 여러번 시도하여 맞추는 해킹을 방지하기 위해 암호를 확인할 때 의도적으로 느리게 설정
    • BcryptPasswordEncoder는 강도를 설정할 수 있는데 강도가 높을수록 오랜 시간이 걸린다.
  • Pbkdf2PasswordEncoder
    • Pbkdf2는 NIST(National Institute of Standards and Technology, 미국표준기술연구소)에 의해서 승인된 알고리즘이고, 미국 정부 시스템에서도 사용
  • ScryptPasswordEncoder
    • 해커가 무작위로 password를 맞추려고 시도할 때 메모리 사용량을 늘리거나 반대로 메모리 사용량을 줄여서 
      느린 공격을 실행할 수밖에 없도록 의도적인 방식을 사용
    • 공격이 매우 어렵고 Pbkdf2보다 안전하다고 평가
    • 보안에 아주 민감한 경우에 사용

 

Security Filter

  • 요청이나 응답 또는 둘 다에 대해 필터링 작업을 수행하는 개체
  • 요청 전, 응답 후 어떤 작업을 하도록 하는게 Filter
  • 필터는 doFilter 메소드에서 필터링을 수행
    • Filter는 doFilter를 구현해야한다.
  • Filter들은 제외할 수도 있고 추가할 수도 있다.
  • Filter 종류
SecurityContextPersistenceFilter

BasicAuthenticationFilter

UsernamePasswordAuthenticationFilter

CsrfFilter

RememberMeAuthenticationFilter

AnonymousAuthenticationFilter

FilterSecurityInterceptor

ExceptionTranslationFilter

 

FilterChainProxy에서 디버깅으로 본 필터 예시

 

 

  • 필터에 동작하는 순서를 정해줘서 원하는대로 유기적으로 동작할 수 있다.
    • FilterOrderRegistration()에서 순서나 개발자가 원하는 커스텀 필터도 적용 할 수 있다.
    • Filter들은 100번 부터 시작해서 100씩 증가
    • 100이 가장먼저 200, 300, 400 이런 순서대로 필터가 적용
    • 100이라는 공백 사이사이에 커스텀 필터를 넣을 수 있다.

 

SecurityContextPersistenceFilter

  •  Async 요청에 대해서도 SecurityContext를 처리할수 있도록 해주는 WebAsyncManagerIntegrationFilter 다음으로 실행되는 필터
  • SecurityContextPersistenceFilter는 SecurityContext를 찾아와서 SecurityContextHolder에 넣어주는 역할
  • SecurityContext를 찾았는데 없다면 새로 하나 생성하고 있으면 그것을 가져온다.
    • 기본적으로는 HttpSession에서 가져온다.
      • 로그인
      • JSESSIONID라는 Session ID를 쿠키를 서버에서 유저에게 준다.
      • 유저가 게시물 조회
      • 쿠키로 세션을 찾고 찾은 세션으로 SecurityContext 찾아서 유저 A를 인증하고 게시물 반환
      • 쿠키로 세션 유지, 쿠키를 삭제하면 로그인 유지 해제
  • JSESSIONID
    • 세션 유지에 필요한 Session ID를 쿠키로 가지고 있어야 세션 유지가 가능하다.
    • 값은 JSESSIONID라는 key에 넣어서 가지고 있다.

 

BasicAuthenticationFilter

// Basic Authentication을 비활성화합니다.
http.httpBasic().disable();

 

// Basic Authentication을 활성화합니다.
http.httpBasic();

  • curl -u user:user -L http://localhost:8080/note
  • BasicAuthenticationFilter 활성화 하지 않을 때, 로그인을 하지 않으면 다른 페이지에 접근이 불가능 했지만, BasicAuthenticationFilter 활성화 하면, 일회성으로 페이지를 불러올 수 있었다.
  • 로그인이라고 부르는 과정이 없어도 username : user123 / password : pass123 라는 로그인 데이터를 
    Base64로 인코딩해서 모든 요청에 포함해서 보내면 BasicAuthenticationFilter는 이걸 인증한다.
  • 세션이 필요 없고 요청이 올때마다 인증이 이루어진다.
    • stateless하다.
  • 요청할 때마다 아이디와 비밀번호가 반복해서 노출되기 때문에 보안에 취약
  • BasicAuthenticationFilter를 사용할 때는 반드시 https를 사용하도록 권장

 

UsernamePasswordAuthenticationFilter

  • Form 데이터로 username, password 기반의 인증을 담당하는 필터
  • 필터 기능 순서
  1. UsernamePasswordAuthenticationFilter : username, password 인증 필터
  2. ProviderManager(AuthenticationManager) : 인증 정보 제공 관리자
  3. AbstractUserDetailsAuthenticationProvider : 인증 정보 제공(계정의 상태나 패스워드 일치 여부등을 파악)
  4. DaoAuthenticationProvider : 유저 정보 제공
  5. UserDetailsService : 유저 정보 제공하는 Service
  • ProviderManager(AuthenticationManager) 
    • 인자로 받은 authentication이 유효한지 확인하고 authentication을 반환
    • 인증하면서 계정에 문제가 있는 것이 발견되면 AuthenticationException를 throw
    • AuthenticationManager는 authenticate 하나만 구현하면 된다.
      • AuthenticationManager를 구현한 Class가 ProviderManager
    • ProviderManager는 Password가 일치하는지, 계정이 활성화 되어있는지를 확인한 뒤 authentication을 반환

  • DaoAuthenticationProvider (AbstractUserDetailsAuthenticationProvider)
    • 유저정보를 가져오는 Provider

CsrfFilter

  • CsrfAttack을 방어하는 필터
  •  Csrf Token을 사용하여 위조된 페이지의 악의적인 공격을 방어
  • CsrfAttack
    • 가짜 URL로 페이지에 접속하게 만들어서 정상적인 시스템에 악의적인 요청을 하게 만드는 공격
    • 방어하는 방법
      • CSRF토큰으로 진위 확인
      • 악의적인 요청시 CSRF 토큰의 포함 여부를 보고 없으면 접근이 불가
  • 정상적인 페이지는 Csrf Token이 있을 것이고 위조된 페이지는 Csrf Token이 없거나 잘못된 Csrf Token을 가지고 있다.
  • 정상적인 페이지에는 Csrf Token 값을 알려줘야 하는데 Tymeleaf에서는 페이지를 만들때 자동으로 Csrf Token값을 넣어주기 때문에 form tag안에 자동으로 생성
<input type="hidden" name="_csrf" value="594af42a-63e9-4ef9-aeb2-3687f12cdf43"/>
  • 자동으로 활성화되어있는 Filter지만 명시적으로 On 하기 위해서는 http.csrf(); 코드를 추가하고, Off 하기 위해서는  http.csrf().disable();를 통해 비활성화

 

RememberMeAuthenticationFilter

  • 일반적인 세션보다 훨씬 오랫동안 로그인 사실을 기억할 수 있도록 설정
  • 로그인 유지하기 기능에 사용
  • Session의 세션 만료 시간은 기본 설정이 30분이지만 RememberMeAuthenticationFilter의 기본 설정은 2주
  • Remember-me 쿠키를 통해 세션을 지속시켜준다.
    • 서버를 재시작하면 브라우저 측에는 쿠키가 남아있지만 서버에서는 사라지기 때문에 다시 로그인을 수행해야한다.
// Remember-Me를 활성화합니다.
http.rememberMe();

<div>
      <span>로그인 유지하기</span>
      <input type="checkbox" id="remember-me" name="remember-me" class="form-check-input mt-0" autocomplete="off">
</div>

 

AnonymousAuthenticationFilter

  • 인증이 안된 유저가 요청을 하면 Anonymous(익명) 유저로 만들어 Authentication에 넣어주는 필터
  • 인증되지 않았다고 하더라도 Null을 넣는게 아니라 기본 Authentication을 만들어 주는 개념
  • 다른 Filter에서 Anonymous유저인지 정상적으로 인증된 유저인지 분기 처리를 할 수있다.

ContextHolder로 anonymous 확인

// AnonymousFilter 활성화
http.anonymous().principal(new User());

 

FilterSecurityInterceptor

  • SecurityContextPersistenceFilter, UsernamePasswordAuthenticationFilter, AnonymousAuthenticationFilter 에서는 SecurityContext를 찾거나 만들어서 넘겨준다.
  • 넘어온 authenticaiton의 내용을 기반으로 최종 인가 판단을 내리는 필터 ->  필터중에 뒤쪽에 위치
  • 인증(Authentication)을 가져오고 만약에 인증에 문제가 있다면 AuthenticationException를 발생시킨다.
  • 인증에 문제가 없다면 해당 인증으로 인가를 판단
  • 인가가 거절된다면 AccessDeniedException를 발생하고 승인된다면 정상적으로 필터가 종료
  • 필터 처리 순서
  1. FilterSecurityInterceptor.doFilter()
  2. AbstractSecurityInterceptor.beforeInvocation()
  3. AbstractSecurityInterceptor.authenticateIfRequired()
    1. 인증에 문제가 있으면 AuthenticationException 예외 발생
  4. AbstractSecurityInterceptor.attemptAuthorization()
    1. 인가에 문제가 있으면 AccessDeniedException 예외 발생

 

 

ExceptionTranslationFilter

  • 최종적으로 발생하는 인증과 인가에 대한 후처리를 할 수 있는 필터
  •  FilterSecurityInterceptor에서 발생할 수 있는 두가지 Exception을 처리해주는 필터
    • AuthenticationException : 인증에 실패할 때 발생
    • AccessDeniedException : 인가에 실패할 때 발생
  • ExceptionTranslationFilter의 handleSpringSecurityException는 Exception의 종류에 따른 로직을 분산
  • EX)
    • AuthenticationException 발생 또는 Anonymous의 AccessDeniedException 발생하면 Login Page로 이동처리
    • 기명 유저의 AccessDeniedException 발생하면  403 Forbidden Whitelabel Error Page로 이동
  • Exception에 대한 대처 방식은 변경할 수 있다.

'개발 > Spring' 카테고리의 다른 글

JWT(Json Web Token)  (0) 2024.03.01
Spring Security Config  (1) 2024.03.01
Spring security Test  (0) 2024.02.28
Spring Security 구현  (0) 2024.02.27
Spring Security  (0) 2024.02.27