[데브코스] Spring Security - 4
Updated: Categories: TILW16D3 - 인증 Event / 보안관련 Header / CsrfFilter / Basic 인증 / Async 처리에 대해 알아보자
AbstractAuthenticationEvent
- 인증 성공, 실패시 이벤트가 발생하고 컴포넌트는 해당 이벤트를 구독할 수 있다.
AbstractAuthenticationEvent
에는 여러 종류가 있다.AuthenticationSuccessEvent
: 인증 성공시 이벤트 발생AuthenticationFailureEvent
: 인증 실패시 이벤트 발생
- 이런 이벤트를 발행해주는건
AuthenticationEventPublisher
인터페이스이다.- 기본 구현체는
DefaultAuthenticationEventPublisher
- 기본 구현체는
커스텀
- 인증 과정은
AuthenticationProcessingFilter
를 통해 진행되므로 관련 이벤트도 해당 클래스에서 발생한다.AuthenticationProcessingFilter
클래스(구현체 포함)를 상속해서 커스텀이 가능하다.- 하지만! 이렇게 구현한다면 기능 확장에 높은 결합도를 가지게 됨. (커스텀 클래스를 계속 변경해야함)
- 개방폐쇄원칙(OCP) 위반
- 따라서 상속 방식을 사용하지 않고,
@EventListener
를 사용하여 동일하게 구현이 가능하다
@Component
public class CustomAuthenticationEventHandler {
@EventListener
public void handleAuthenticationSuccessEvent(AuthenticationSuccessEvent event) {
인증 성공 이벤트를 구독한다
}
@EventListener
public void handleAuthenticationFailureEvent(AbstractAuthenticationFailureEvent event) {
인증 실패 이벤트를 구독한다
}
}
- 해당 이벤트 핸들러는 Bean으로 띄워 이벤트를 감시하게 한다
@EventListener
는 동기적이므로 리스너에서 처리가 지연되면 이벤트 발행 부분에서도 지연된다.- 이는 비동기로 처리가 가능하다.
WebMvcConfigurer
설정 클래스에서@EnableAsync
를 추가해주고, 비동기처리할 메소드에@Async
를 달아주면 됨.
@EnableAsync
@Configuration
public class WebMvcConfigure implements WebMvcConfigurer {
...
}
HeaderWriterFilter
- response 헤더에 보안 관련 헤더를 추가해주는 필터
- 관련 이슈에 대해 기본적인 방어 기능만 제공한다. 즉 완벽한 방어는 아님.
- 브라우저마다 다르게 동작할 수 있다.
- 실제로 아래와 같은 헤더목록이 자동으로 추가되어 날라온다
XContentTypeOptionsHeaderWriter
- 브라우서에서 MIME sniffing을 사용하여 Request Content Type 을 추측 할 수 있는데 이것은 XSS 공격에 악용될 수 있음
- 지정된 MIME 형식 이외의 다른 용도로 사용하고자 하는 것을 차단
XXssProtectionHeaderWriter
- 브라우저에 내장된 XSS(Cross-Site Scripting) 필터 활성화
- 웹 상에서 가장 기초적인 취약점 공격 방법의 일종으로, 악의적인 사용자가 공격하려는 사이트에 스크립트를 넣는 기법을 말함
- 일반적으로 브라우저에는 XSS공격을 방어하기 위한 필터링 기능이 내장되어 있음
- 물론 해당 필터로 XSS공격을 완벽하게 방어하지는 못하지만 XSS 공격의 보호에 많은 도움이 됨
CacheControlHeadersWriter
- 캐시를 사용하지 않도록 설정
- 브라우저 캐시 설정에 따라 사용자가 인증 후 방문한 페이지를 로그 아웃한 후 캐시 된 페이지를 악의적인 사용자가 볼 수 있음
XFrameOptionsHeaderWriter
- clickjacking 공격 방어
- 웹 사용자가 자신이 클릭하고 있다고 인지하는 것과 다른 어떤 것을 클릭하게 속이는 악의적인 기법
- 보통 사용자의 인식 없이 실행될 수 있는 임베디드 코드나 스크립트의 형태
HstsHeaderWriter
- HTTP 대신 HTTPS만을 사용하여 통신해야함을 브라우저에 알림 (HTTPS 설정 시 관련 헤더 추가됨)
CsrfFilter
CSRF 공격
- Cross-Site Request Forgery
- 로그인 상태인 사용자의 브라우저를 사용해 악성유저가 요청을 대신 보낼 수 있게됨
방어 방법
- Referrer 검증 : Request의 referrer를 확인하여 domain이 일치하는지 확인
- CSRF Token 활용
- 사용자의 세션에 임의의 토큰 값을 저장하고 (로그인 완료 여부와 상관없음), 사용자의 요청 마다 해당 토큰 값을 포함 시켜 전송
- 리소스를 변경해야하는 요청(POST, PUT, DELETE 등)을 받을 때마다 세션에 저장된 토큰 값과 요청 파라미터에 전달되는 토큰 값이 일치하는 지 검증
- 브라우저가 아닌 클라이언트에서 사용하는 서비스의 경우 CSRF 보호를 비활성화 할 수 있음
- 정상 사이트의 form 태그에는 csrf 토큰이 숨겨져있음!
CsrfTokenRepository
- csrf 토큰을 저장하는 인터페이스
- default로
HttpSessionCsrfTokenRepository
클래스가 사용된다
BasicAuthenticationFilter
- 인증 방식중 하나인 Basic 인증을 처리해주는 필터
- HTTPS 프로토콜에서만 제한적으로 사용하며, 잘 사용하지 않는다
- 내부 시스템간 통신에서만 사용
동작 원리
- 1. 사용자가 username, password로 로그인 요청
- 2. HTTP 요청 헤더에 username과 password를 Base64 인코딩,
Basic
prefix를 붙여 담기username:password
Base64 인코딩 -> ex)dXNlcjp1c2VyMTIz
- prefix 붙이기 -> ex)
Basic dXNlcjp1c2VyMTIz
- 3.
BasicAuthenticationFilter
에서 요청을 받아BasicAuthenticationConverter
에서 Base64 디코딩 - 4. 디코딩된 정보로
UsernamePasswordAuthenticationToken
생성
사용법
- 필터 체이닝에
.httpBasic()
만 달아주면 된다
http.
...
.httpBasic()
WebAsyncManagerIntegrationFilter
- Spring MVC Async Request (반환 타입이
Callable
) 처리에서SecurityContext
를 공유할수 있게 하는 필터
Spring의 스레드 구조
사용법
Async Controller 메소드
@RequestMapping(...)
public Callable<T>> 메소드() {
Callable<T> callable = () -> {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
...
}
return callable;
}
- 다음과 같이 Controller에서
Callable
타입을 사용하면 비동기 처리가 수행됨 - WAS 스레드와 서로 다른 스레드임에도 불구하고 WAS 스레드와 동일한
Authentication
객체를 가져올 수 있다.SecurityContextCallableProcessingInterceptor
클래스를 사용하여 WAS 스레드의 ThreadLocal(SecurityContext)을 참조함
- 하지만 이 기능은 Spring MVC Async Request, 즉 Controller 계층에서만 해당된다.
- 그럼 Service 계층에서 Async를 사용하여 동일한
Authentication
객체를 가져오고 싶다면 어떻게 할까?SecurityContextHolderStrategy
설정을 바꿔주면 된다. (ThreadLocal
변수에 대한 설정)
WebSecurityConfigure 클래스
@Configuration
@EnableWebSecurity
public class WebSecurityConfigure extends WebSecurityConfigurerAdapter {
public WebSecurityConfigure() {
SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);
}
...
}
WebSecurityConfigure
의 생성자에서SecurityContextHolder
전략을MODE_INHERITABLETHREADLOCAL
로 변경한다.- default는
MODE_THREADLOCAL
전략임 MODE_INHERITABLETHREADLOCAL
은 스레드 로컬 변수를 상속(?)하는 전략
- default는
- 하지만 역시 문제점이 존재한다
- Pooling 처리된
TaskExecutor
와 함께 사용시 ThreadLocal의 clear 처리가 제대로되지 않아 문제될 수 있음 - Pooling 되지 않는
TaskExecutor
와 함께 사용해야 함
- Pooling 처리된
DelegatingSecurityContextAsyncTaskExecutor
- 스레드 로컬 변수인
SecurityContext
를 다른 스레드에 전파해주는 클래스 - Bean으로 떠있다가 Async로 스레드가 생성되어 작업될 때 WAS의 스레드 로컬 변수를 참조하여 가져온다.
- 아래와 같이 설정해줘야 한다
WebSecurityConfigure 클래스
@Bean
@Qualifier("myAsyncTaskExecutor")
public ThreadPoolTaskExecutor threadPoolTaskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(3); // 기본 생성 풀 사이즈
executor.setMaxPoolSize(5); // 최대 풀 사이즈
executor.setThreadNamePrefix("my-executor"); // 스레드 prefix 지정
return executor;
}
@Bean
public DelegatingSecurityContextAsyncTaskExecutor taskExecutor(
@Qualifier("myAsyncTaskExecutor") AsyncTaskExecutor delegate
) {
return new DelegatingSecurityContextAsyncTaskExecutor(delegate);
}