용로그
article thumbnail

서론


이메일 발송 기능을 개발하고 있었다. 이메일 발송은 시간이 약간 걸리는 작업인지라 클라이언트에게 응답을 곧바로 반환하지 못하는 문제가 있었다.

 

그래서 이메일 발송 기능은 비동기(Async)로 처리했다. 특히 인증코드에 만료 시간(ttl)이 존재해야 했기에, 이메일 발송 로직에는 인증 코드를 레디스에 저장하는 태스크도 포함되어 있었다.

 

인증코드를 저장하는 메서드

 

SecurityContext가 Authentication을 가지고 있지 않은 건에 대하여

그런데 문제는 여기서 시작되었다. AuthorityCode(인증 코드) 객체를 만드려면 현재 로그인한 멤버의 아이디가 필요했고, 현재 로그인한 멤버의 정보를 가져오는 로직은 SecurityContextHolder에서 가져온다.

 

 

내가 짠 코드기도 했고 코드 퀄리티를 더 챙기다보니 제대로 동작하는지 확인조차 못하고 있었다. 커밋 전 테스트 케이스와 API 테스트가 모두 통과하기를 바라고 있었는데, 아래와 같은 예외가 발생했다.

 

context holder authentication null

 

정확한 예외명은 아니지만, 확실한 것은 SecurityContext에 Authentication(인증) 객체가 존재하지 않는다는 것이었다. 기존에는 잘 동작하던 SecurityContext였지만, 비동기 환경 문제일 것이라곤 상상조차 못하고 있었다.

 

다시 차근차근 생각해보고 있었는데, 찰나에 생각이 났다. Spring Security는 Thread Local 구조를 사용한다는 것이다. Thread Local이란 하나의 스레드 내에서만 정보를 공유하는 구조인데, 스프링 시큐리티는 이 구조가 Default로 설정되어 있다.

 

비동기로 동작하는 메서드일 경우 Thread Local 구조로 되어있는 Spring Security 환경에서 하나의 Authentication을 공유할 수 없다. 따라서 SecurityContext를 현재 스레드 하위에 생성된 스레드까지 SecurityContext를 공유하도록 해야한다.

 

SecurityContextHolder 전략


SecurityContextHolder 전략들

  • MODE_THREADLOCAL: 기본 전략이며, 같은 Thread 내부에서만 SecurityContext를 공유한다. 비동기 환경에서 사용하기 적합하지 않다.
  • MODE_INHERITABLETHREADLOCAL: 이름처럼 하위 Thread가 생성되면 해당 Thread까지 SecurityContext를 공유한다. 여러개의 스레드가 SecurityContext를 공유하고 싶을 때 사용한다.
  • MODE_GLOBAL: Application의 모든 스레드에 SecurityContext를 공유한다.
  • MODE_PRE_INITIALIZED: SecurityContextHolder를 미리 초기화한다. 이 전략을 사용하면 애플리케이션 실행 중 SecurityContextHolder를 직접 초기화할 필요가 없다.

 

만약 filterChain을 오버라이딩 해서 filter들을 설정하고 있다면, 이를 활용하여 SecurityConfig 클래스를 아래와 같이 변경해줄 수 있다. 

 

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    ....
    SecurityContextHolder.setStrategyName(MODE_INHERITABLETHREADLOCAL);
    return http.build();
}

 

MODE_INHERITABLETHREADLOCAL 전략의 단점

위와 같이 설정한다면 깔끔하게 스레드 로컬과 비동기 문제를 해결할 수 있지만, 스프링 시큐리티가 MODE_THREADLOCAL을 Defualt로 채택한 이유가 있을것이다.

 

찾아봐도 잘 나와있지 않아서 내가 생각한 해당 전략의 단점을 소개해보겠다.

  1. 성능 저하: 스레드 계층 구조에서 상속을 지원하기 위해 추가적인 로직을 실행해야 할 것이다. 알아본 바로 MODE_THREADLOCAL의 구조는 상속을 구현하기 위해 링크드 리스트를 사용한다. 즉, 스레드 로컬 값을 상속할 때 링크드 리스트를 탐색해야 하기 때문에 성능이 저하될 수 있을 것이다.
  2. 사이드 이펙트: 상속을 지원하는 스레드 구조이기에, 전략을 제대로 이해하지 못하고 사용한다면 내가 짠 코드여도 예상치 못한 방식으로 동작할 수 있을 것이다.

 

비동기라는 개념이 코드단에서는 고려해야하는 부분이 많다고 느껴지는 시간이었다. 굉장히 오래 삽질을 했지만, 오랜만에 스프링 시큐리티를 다시 공부한 느낌이라 의미 없는 시간은 아니었다.

profile

용로그

@용로그

벨덩보단 용덩 github.com/wonyongChoi05