22/06/09 Spring Security 아키텍처

김석진·2022년 6월 9일
0

다시 초심으로

목록 보기
19/19

Spring Security 내부구조


Spring Security는 이렇게 겹겹히 쌓여져 있는 구조로 이뤄져 있다
SecurityContextHolder->SecurityCotnext->Authentication->Pricipal&GrantAuthority

  • SecurityContextHolder
    • SecurityContextHolder는 SecurityContext를 제공하는 static 메소드(getContext)를 지원한다.
  • SecurityContext
    • SecurityContext는 접근 주체와 인증에 대한 정보를 담고있는 Context, 즉 Authentication을 담고있음
  • Authentication
    • Pricipal과 GrantAuthority를 제공, 인증이 이뤄지면 해당 Authentication이 저장됨
  • Pricipal
    • 유저에 해당하는 정보, 대부분 Pricipal로 UserDetails를 반환
  • GrantAuthority
    • ROLEADMIN,ROLE_USER등 Pricipal이 가지고있는 권한을 나타냄
      prefix로 'ROLE
      '이 붙음
      인증 이후 인가를 할때 사용
      권한은 여러개일수 있기 때문에 Collection< GrantedAuthority > 형태로 제공

ThreadLocal

WebMVC기반으로 프로젝트를 만든다는 가정하에 대부분의 경우에는 요청1개에 Thread 1개가 생성된다.

이때 ThreadLocal을 사용하면 Thread마다 고유한 공간을 만들수 있고 그곳에 SecurityContext를 저장할 수 있다.

그러나 ThreadLocal만 강제로 사용해야하는 것은 아니며 원하는 SecurityContext 공유 전략을 바꿀 수 있다.
공유 전략을 바꾸는 방법은 3가지가 있다.
1. MODE_THREADLOCAL(가장 기본설정 모드)
2. MODE_INHERITABLETHREADLOCAL
3. MODE_GLOBAL

ThreadLocal 더자세히

Spring Security의 기본적인 Security Context 관리젹략은 ThreadLocal을 사용하면 ThreadLocalSecurityContextHolderStrategy이다.

변수는 지역변수, 전역변수와 같은 유효한 Scope를 가진다.
마찬가지로 ThreadLocal은 Thread마다 고유한 영역을 가지고 있는 곳에 저장된 변수로 각각의 Thread 안에서 유효한 변수이다.
일반적인 서버의 경우에는 외부로 부터 요청이 오면 그 요청마다 Thread 1개가 할당됨. 따라서 ThreadLocal로 SecurityCotnext를 관리하게 되면 SecurityContext는 요청마다 독립적으로 관리될 수 있다.

PasswordEncoder


이값이 PasswordEncoder가 만든 값이다.
Spring Security를 사용하면서 유저의 Password를 관리해야할 필요가 있다.
Password를 관리할 때는 일단 두기자가 만족되어야한다.

  1. 회원가입을 할때 Password를 입력받으면 그 값을 암호화해서 저장
  2. 로그인할 때 입력받은 Password와 회원가입시 password를 비교할 수 있어야함

이 두가지를 만족하기 위해 보통 해시 함수라는 알고리즘 방식을 이용
해시함수는 암호화는 비교적쉽지만 복호화가 거의 불가능한 방식의 알고리즘 이것을 사용하면 아래와 같은 방식으로 password를 관리할 수 있다.

  1. 회원가입시 password를 해시함수로 암호화해서 저장
  2. 로그인시 passwrod가 들어오면 같은 해시함수로 암호화함
  3. 저장된 값을 불러와서 2번의 암호화된 값과 비교
  4. 동일하면 같은 암호로 인지

PasswordEncoder가 어떤 내용을 담고있는지 보자

암호화하는 Encode와 같은값인지 확인하는 matches가 있다. 복호화된 메소드는 없다.

다양한 PasswordEncoder 전략이 있는데 그종류는 아래와같다.

스프링 시큐리티는 DelegatingPasswordEncoder라는 표의 모든 PasswordEncoder를 선택할수있는 대표 PasswordEncoder를 따로 만들어서 사용하고 있다.
예시를 보면 뒤쪽의 난해한문자들이 암호화된값들같이보임
괄호({ })안의 단어들은 어떤 안호화 방식을 사용했는지 표시해둔것이다.
DelegatingPasswordEncoder는 어떤 PasswordEncoder를 선택했는지 알려주기 위해서 앞에 암호화방식을 표현하고있다. 그렇기 때문에 어떤 암호는 bcrypt로 암호화되고 다른암호는 sha256으로 되었다고 해도 DelegatingPasswordEncoder는 둘다 지원할 수있다.

PasswordEncoder 종류

  • NoOpPasswordEncoder
    • 암호화X, 평문을 사용
    • password가 그대로 노출 현재는 deprecated가 되었고 사용하는것 권장X
  • BcryptPasswordEncoder
    • Bcrypt 해시 함수를 사용한 PasswordEncoder
    • Bcrypt는 애초부터 패스워드 저장을 목적으로 설계
    • Password를 무작위로 여러번 시도해서 맞추는 해킹을 방지하기 위해 암호를 확인할 때 의도적으로 느리게 설정됨
    • BycrpyptPasswordEncoder는 강도를 설정할 수 있고 강도가 높을 수록 오랜시간이 걸림
  • Pbkdf2PasswordEncoder
    • Pbkdf2는 NIST(National Institute of Standards and Technololy, 미국표준기술연구소)에 의해서 승인된알고리즘, 미국 정부 시스템에서도 사용
  • ScryptPasswordEncoder
    • Scrypt는 Pbkdf2와 유사
    • 해커가 무작위로 password 맞추려고 시도시 메모리 사용량을 늘리거나 반대로 메모리 사용량을 줄여서 느린 공격을 실행할 수 밖에 없도록 의도적인 방식을 사용
    • 따라서 공격이 매우 여럽고 Pbkdf2보다 안전하다고 평가
    • 보안에 아주 민감한 경우 사용할 수 있음

Security Filter

Spring Security의 동작은 사실상 Filter로 동작한다고 해도 무방
다양한 필터들이 존재, 이 Filter들은 각자 다른 기능을 하고 있음
이런 Filter들은 제외할 수도있고 추가할 수 도있다. 필터에 동작하는 순서를 정해줘서 원하는 대로 유기적으로 동작할 수 있다.
(확장성이 뛰어나다)

Filter

SecurityContextPersistenceFilter를 보면 아래처럼 GenericFilterBean를 상속하고 있고 GenericFilterBean는 Filter를 상속하고 있다.
즉, SecurityContextPersistenceFilter는 Filter를 구현한다.

SpringSecurity에는 다양한 Filter가 존재하는데 그중 하나가 SecurityContextPersistenceFilter일 뿐이다.
Filter란 요청이나 응답 또는 둘다에 의해 필터링 작업을 수행하는 개체이다. 필터는 doFilter 메소드에서 필터링을 수행한다.
즉 Filter는 doFilter를 구현해야한다.

간단히 생각하면 요청전, 응답 후 어떤 작업을 하도록하는게 Filter이다.

Filter가 여러개인 상황이면 아래처럼 동작함
마지막 순서 필터가 안쪽부터 첫번째 필터의 가장 밖까지 감싸고 있는 혀앹라고 볼수 있다.

Security Filter

다양한 필터들과 그 필터들의 동작순서는 FilterChainProxy Class에서 dofilterInternal에 break point를 걸어서 디버깅해보면 알 수 있다.

SecurityContextPersistenceFilter

SecurityContextPersistenceFilter는 보통 두번째로 실행되는 필터
(첫번째로 실행되는 필터는 Async 요청에 대해서도 SecurityContext를 처리할 수 있도록 해주는 WebAsyncManagerIntegrationFilter이다)
SecurityContextPersistenceFilter는 SecurityContext를 찾아와서 SecurityContextHolder에 넣어주는 역할을 하는 Filter이다. 만약 SecurityContext를 찾았는데 없다면 그냥 새로 하나 만들어준다.

HttpSession

SecurityContextPersistenceFilter는 SecurityContext가 있으면 그걸 사용가져오고 없으면 새로만든다고 했따.
그럼 가져온다는건 어디서 가져온다는 것일까?
가져올 방법은 많지만 기본적으로 HttpSession에서 가져온다.

로그인을하면 JSESSIONID라는 쿠키를 받는다.
유저 A브라우저는 그 쿠키를 저장하고 게시글을 보여달라고할때 쿠키를 같이 요청을 보낸다.
서버에서 쿠키를 이용해서 SecurityContexrt를 찾고 유저의 게시글을 응답으로 보내게된다.

JSESSIONID

세션 유지에 필요한 Session ID를 쿠키로 가지고 있어야함
그 값은 JSESSIONID라는 key에 넣어서 가지고 있다.

JSESSIONID가 없다면 세션을 찾을 수없기때문에 SecurityContext를 가져올 수 없고 누가 로그인을 했는지 알수없기떄문에 인증이 되지않는다.

BasicAuthenticationFilter

직접 필터를 적용해보면 따로 로그인이라는 과정을 하지 않았는데도 일회성으로 페이지를 불러올 수 있었다.
로그인이라는 과정이 없어도 로그인 데이터를 Base64로 인코딩해서 모든 요청에 포함해서 보내면 BasicAuthenticationFilter는 이걸인증함
그렇기 때문에 세션이 필요없고 요청이 올때마다 인증이 이뤄짐(즉 stateless함(상태를저장하지않는다는 뜻))
이런 방식을 요청할 때마다 아이디와 비밀번호가 반복해서 노출되기 때문에 보안에 취약하다.
그렇기 때문에 BasicAuthenticationFilter를 사용할 때는 반드시 https를 사용하도록 권장

BasicAuthenticationFilter를 사용하지 않을것이라면 명시적으로 disable 시켜주는것이 좋다.

UsernamePasswordAuthenticationFilter

UsernamePasswordAuthenticationFilter는 Form데이터로 username,password 기반의 인증을 담당하고 있는 필터이다.
로그인을 하면 그것을 인증하는 필터이다.

CsrfFilter

CsrfFilter는 CsrfAttack을 방어하는 filter이다.

Csrf Attack이란?



송금시스템에서 CSRF 토큰을 이용해서 진위여부를 판단할 수 있다.

즉, CsrfFilter는 Csrf Token을 사용하여 위조된 페이지의 악의적인 공격을 방어함
정상적인 페이지는 Csrf Token이 있을 것이고 위조된 페이지는 Csrf Token이 없거나 잘못된 Csrf Token을 가지고 있다.
따라서 정상적인 페이지에서는 Csrf Token 값을 알려줘야 하는데 Thymeleaf 에서는 페이지를 만들때 자동으로 Csrf Token을 넣어준다.
따로 추가하지 않았는데 아래와 같은 코드가 Form tag안에 자동으로 생성되며 사용자에게 보여줄 필요가 없는 값이기 때문에 hidden으로 처리됨

RememberMeAuthenticationFilter

RememberMeAuthenticationFilter는 일반적인 세션보다 훨씬 오랫동안 로그인 사실을 기억할 수 있도록 해준다.
Session의 세션 만료 시간은 기본 30분이지만 RememberMeAuthenticationFilter의 기본 설정은 2주이다.

AnonymousAuthenticationFilter

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

FilterSecurityInterceptor

Interceptor로 끝나지만 Filter중 하나이다.
앞서 본 SecurityContextPersistneceFilter, UsernamePasswordAuthenticationFilter,AnonymousAuthenticationFilter에서 SecurityContext를 찾거나 만들어서 넘겨주고 있음을 확인했다.
FilterSecurityInterceptor에서는 이렇게 넘어온 authentication의 내용을 기반으로 최종 인가 판단을 내린다.
그렇기 때문에 대부분의 경우에는 필터중 뒤쪽에 위치한다.
먼저, 인증(Authentication)을 가져오고 만약 인증에 문제가 있다면 AuthenticationException을 발생시킨다.
인증에 문제가 없다면 해당 인증으로 인가를 판단한다.
이때 인가가 거절된다면 AccessDeniedException를 발생하고 승인된다면 정상적으로 필터가 종료

ExceptionTranslationFilter

앞서본 FilterSecurityInterceptor에서 발생할 수 있는 두가지 Exception을 처리해주는필터
1. AuthenticaitionException: 인증에 실패할 떄 발생
2. AccessDeniedException: 인가에 실패할 떄 발생
즉, 인증이나 인가에 실패했을때 어떤 행동을 취해야하는지를 결정해주는 Filter

후처리를 하는 방법에 대해서 살펴보자

profile
주니어 개발자 되고싶어요

0개의 댓글