내부 구조
SecurityContextHolder -> SecurityContext -> Authentication -> Principal & GrantAuthority
- SecurityContextHolder
- SecurityContext를 제공하는 static 메소드(getContext)를 지원
- SecurityContext
- 접근 주체와 인증에 대한 정보(Authentication)를 담고 있는 Context
SecurityContext securityContext = SecurityContextHolder.getContext();
- 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를 공유
- MODE_THREADLOCAL
- ThreadLocal은 Thread마다 고유한 영역을 가지고 있는 곳에 저장된 변수로 각각의 Thread안에서 유효한 변수
- 일반적인 서버의 경우에는 외부로부터 요청이 오면 그 요청마다 Thread 1개가 할당
- ThreadLocal로 SecurityContext를 관리하게 되면 SecurityContext는 요청마다 독립적으로 관리될 수 있다.
PasswordEncoder
- Password 관리
- 회원가입할 때 Password를 입력받으면 그 값을 암호화해서 저장해야한다.
- 로그인할 때 입력받은 Password와 회원가입할 때의 Password를 비교할 수 있어야한다.
- 해시 함수 알고리즘 방식
- 암호화는 비교적 쉽지만 복호화가 거의 불가능한 방식의 알고리즘
- 회원가입할 때 password를 해시함수로 암호화해서 저장
- 로그인할 때 password가 들어오면 같은 해시함수로 암호화
- 저장된 값을 불러와서 2번의 암호화된 값과 비교
- 동일하면 같은 암호로 인지
PasswordEncorder 종류
- 어떤 암호는 bcrypt로 암호화되고 다른 암호는 sha256 되었다고 하더라도 DelegatingPasswordEncoder는 둘다 지원할 수 있다.
- BcryptPasswordEncoder
- Bcrypt 해시 함수를 사용한 PasswordEncoder
- Password를 무작위로 여러번 시도하여 맞추는 해킹을 방지하기 위해 암호를 확인할 때 의도적으로 느리게 설정
- BcryptPasswordEncoder는 강도를 설정할 수 있는데 강도가 높을수록 오랜 시간이 걸린다.
- Pbkdf2PasswordEncoder
- Pbkdf2는 NIST(National Institute of Standards and Technology, 미국표준기술연구소)에 의해서 승인된 알고리즘이고, 미국 정부 시스템에서도 사용
- ScryptPasswordEncoder
- 해커가 무작위로 password를 맞추려고 시도할 때 메모리 사용량을 늘리거나 반대로 메모리 사용량을 줄여서
느린 공격을 실행할 수밖에 없도록 의도적인 방식을 사용 - 공격이 매우 어렵고 Pbkdf2보다 안전하다고 평가
- 보안에 아주 민감한 경우에 사용
- 해커가 무작위로 password를 맞추려고 시도할 때 메모리 사용량을 늘리거나 반대로 메모리 사용량을 줄여서
Security Filter
- 요청이나 응답 또는 둘 다에 대해 필터링 작업을 수행하는 개체
- 요청 전, 응답 후 어떤 작업을 하도록 하는게 Filter
- 필터는 doFilter 메소드에서 필터링을 수행
- Filter는 doFilter를 구현해야한다.
- Filter들은 제외할 수도 있고 추가할 수도 있다.
- Filter 종류
SecurityContextPersistenceFilter
BasicAuthenticationFilter
UsernamePasswordAuthenticationFilter
CsrfFilter
RememberMeAuthenticationFilter
AnonymousAuthenticationFilter
FilterSecurityInterceptor
ExceptionTranslationFilter
- 필터에 동작하는 순서를 정해줘서 원하는대로 유기적으로 동작할 수 있다.
- FilterOrderRegistration()에서 순서나 개발자가 원하는 커스텀 필터도 적용 할 수 있다.
- Filter들은 100번 부터 시작해서 100씩 증가
- 100이 가장먼저 200, 300, 400 이런 순서대로 필터가 적용
- 100이라는 공백 사이사이에 커스텀 필터를 넣을 수 있다.
SecurityContextPersistenceFilter
- Async 요청에 대해서도 SecurityContext를 처리할수 있도록 해주는 WebAsyncManagerIntegrationFilter 다음으로 실행되는 필터
- SecurityContextPersistenceFilter는 SecurityContext를 찾아와서 SecurityContextHolder에 넣어주는 역할
- SecurityContext를 찾았는데 없다면 새로 하나 생성하고 있으면 그것을 가져온다.
- 기본적으로는 HttpSession에서 가져온다.
- 로그인
- JSESSIONID라는 Session ID를 쿠키를 서버에서 유저에게 준다.
- 유저가 게시물 조회
- 쿠키로 세션을 찾고 찾은 세션으로 SecurityContext 찾아서 유저 A를 인증하고 게시물 반환
- 쿠키로 세션 유지, 쿠키를 삭제하면 로그인 유지 해제
- 기본적으로는 HttpSession에서 가져온다.
- 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 기반의 인증을 담당하는 필터
- 필터 기능 순서
- UsernamePasswordAuthenticationFilter : username, password 인증 필터
- ProviderManager(AuthenticationManager) : 인증 정보 제공 관리자
- AbstractUserDetailsAuthenticationProvider : 인증 정보 제공(계정의 상태나 패스워드 일치 여부등을 파악)
- DaoAuthenticationProvider : 유저 정보 제공
- 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유저인지 정상적으로 인증된 유저인지 분기 처리를 할 수있다.
// AnonymousFilter 활성화
http.anonymous().principal(new User());
FilterSecurityInterceptor
- SecurityContextPersistenceFilter, UsernamePasswordAuthenticationFilter, AnonymousAuthenticationFilter 에서는 SecurityContext를 찾거나 만들어서 넘겨준다.
- 넘어온 authenticaiton의 내용을 기반으로 최종 인가 판단을 내리는 필터 -> 필터중에 뒤쪽에 위치
- 인증(Authentication)을 가져오고 만약에 인증에 문제가 있다면 AuthenticationException를 발생시킨다.
- 인증에 문제가 없다면 해당 인증으로 인가를 판단
- 인가가 거절된다면 AccessDeniedException를 발생하고 승인된다면 정상적으로 필터가 종료
- 필터 처리 순서
- FilterSecurityInterceptor.doFilter()
- AbstractSecurityInterceptor.beforeInvocation()
- AbstractSecurityInterceptor.authenticateIfRequired()
- 인증에 문제가 있으면 AuthenticationException 예외 발생
- AbstractSecurityInterceptor.attemptAuthorization()
- 인가에 문제가 있으면 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 |