[데브코스] Spring Security - 4

Updated: Categories:

W16D3 - 인증 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 헤더에 보안 관련 헤더를 추가해주는 필터
    • 관련 이슈에 대해 기본적인 방어 기능만 제공한다. 즉 완벽한 방어는 아님.
    • 브라우저마다 다르게 동작할 수 있다.
  • 실제로 아래와 같은 헤더목록이 자동으로 추가되어 날라온다

image

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
  • 로그인 상태인 사용자의 브라우저를 사용해 악성유저가 요청을 대신 보낼 수 있게됨

image

방어 방법

  • 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를 붙여 담기
    1. username:password Base64 인코딩 -> ex) dXNlcjp1c2VyMTIz
    2. prefix 붙이기 -> ex) Basic dXNlcjp1c2VyMTIz
  • 3. BasicAuthenticationFilter에서 요청을 받아 BasicAuthenticationConverter에서 Base64 디코딩
  • 4. 디코딩된 정보로 UsernamePasswordAuthenticationToken 생성

사용법

  • 필터 체이닝에 .httpBasic()만 달아주면 된다
http.
    ...
    .httpBasic()


WebAsyncManagerIntegrationFilter

  • Spring MVC Async Request (반환 타입이 Callable) 처리에서 SecurityContext를 공유할수 있게 하는 필터
Spring의 스레드 구조

스레드 drawio

사용법

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은 스레드 로컬 변수를 상속(?)하는 전략
  • 하지만 역시 문제점이 존재한다
    • Pooling 처리된 TaskExecutor와 함께 사용시 ThreadLocal의 clear 처리가 제대로되지 않아 문제될 수 있음
    • Pooling 되지 않는 TaskExecutor와 함께 사용해야 함

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);
}