모르지 않다는 것은 아는것과 다르다.

Spring Security

SecurityContextHolder, AuthenticationManager, ThreadLocal

채마스 2022. 2. 28. 20:23

SecurityContext, SecurityContextHolder

  • SecuuryContext
    • Authentication 제공한다.
    • Authentication 객체가 저장되는 보관소로 필요 시 언제든지 Authentication 객체를 꺼내어 쓸 수 있도록 제공되는 클래스다.
    • ThreadLocal 에 저장되어 아무 곳에서나 참조가 가능하도록 설계되어 있다.
    • 인증이 완료되면 HttpSession 에 저장되어 어플리케이션 전반에 걸쳐 전역적인 참조가 가능하다.
  • SecurityContextHolder
    • SecurityContext를 제공하고, 기본적으로 ThreadLocal을 사용한다.
    • 위의 정보들은 같은 쓰레드내에서 공유하는 정보이다. -> 만약에 쓰레드가 달라지면? -> SecurityContextHolder가 제공하는 다른 전략을 사용해야한다.
    • 따라서 SecurityContext 객체 저장 방식을 지정할 수 있다.
      • MODE_THREADLOCAL : 스레드당 SecurityContext 객체를 할당, 기본값
      • MODE_INHERITABLETHREADLOCAL : 메인 스레드와 자식 스레드에 관하여 동일한 SecurityContext 를 유지된다.
      • MODE_GLOBAL : 응용 프로그램에서 단 하나의 SecurityContext를 저장한다.
      • 그 전략은 아래와 같이 구현되어있다.
      public class SecurityContextHolder {
          public static final String MODE_THREADLOCAL = "MODE_THREADLOCAL";
          public static final String MODE_INHERITABLETHREADLOCAL = "MODE_INHERITABLETHREADLOCAL";
          public static final String MODE_GLOBAL = "MODE_GLOBAL";
          public static final String SYSTEM_PROPERTY = "spring.security.strategy";
          private static String strategyName = System.getProperty("spring.security.strategy");
          private static SecurityContextHolderStrategy strategy;
          private static int initializeCount = 0;
      
          // ... 이하 생략
      }
      • 아래와 같이 설정할 수 있다.
      @Configuration
      @EnableWebSecurity
      public class SecurityConfig extends WebSecurityConfigurerAdapter {
          @Override
          protected void configure(HttpSecurity http) throws Exception {
              SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);
          }
      }
      • 이렇게 하면 자식 스레드에서도 공유가능하다.
        • 자식스레드? -> 예를들어 로그인하고 다른 페이지로 이동하면 -> 로그인할때의 스레드가 부모, 다른 페이지 요청할 때 쓰레드가 자식이다.
  • SecurityContextHolder 가 ThreadLocal 을 가지고 있고, ThreadLocal 은 SecurityContext 를 가지고 있다.
  • 최종적으로 세션에 저장되는 것을 확인할 수 있다.
  • 아래와 같이 세션에 저장된 Authentication 객체와 SecurityContextHolder 에 담겨있는 Authentication 를 비교해볼 수 있다.
    Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
    HttpSession session;
    Authentication authentication = session.getAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY).getAuthentication;






Authentication

  • 당신이 누구인지 증명하는 인터페이스 이다.
  • User 객체를 가지고 있다.
  • 사용자의 인증 정보를 저장하는 토큰 개념이다.
  • 인증 시 id 와 password 를 담고 인증 검증을 위해 전달되어 사용된다.
  • 인증 후 최종 인증 결과 (user 객체, 권한정보) 를 담고 SecurityContext 에 저장되어 전역적으로 참조가 가능하다.
  • Authentication authentication = SecurityContexHolder.getContext().getAuthentication()
  • 아래와 같은 구조를 가진다.
    • principal : 사용자 아이디 혹은 User 객체를 저장
    • credentials : 사용자 비밀번호
    • authorities : 인증된 사용자의 권한 목록
    • details : 인증 부가 정보
    • Authenticated : 인증 여부
  • Authentication 객체가 만들어지는 과정은 아래와 같다.

  • 사용자의 로그인을 시도하게되면 UsernamePasswrodAutheticationFilter 가 전달받고 Authentication 를 만든다.
  • principal속성에는 사용자의 아이디를 저장하고 Credentials속성에는 패서워드를 저장한다 저장된 정보들은 인증검증을 받기위해서 사용된다.
  • AuthenticationMannager가 인증객체를 가지고 인증처리를 시작한다 인증에 성공되면 Authentication를 새로 만든다.
  • AuthenticationMannager로 받기전의 Authentication 와 받고나서 새로생성된 Authentication 는 동일한타입으로 만든다.
    • 차이점은 principal과 Credentials를 보면되는데 처음에는 사용자의 아이디와 패스워드를 저장하지만 이후에는 인증에 성공한 결과를 담고 Authorities 권한 목록을 담는다.
  • 최종적으로 SecurityContextHolder로 Authentication 를 저장한다.
  • 코드는 아래와 같다.
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
// 사용자 정보
Object principal = authentication.getPrincipal();
// 사용자 권한
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
boolean authenticated = authentication.isAuthenticated();
  • Authentication : Principal과 GrantAuthority 제공한다.
  • Principal
    • “누구"에 해당하는 정보이다.
    • 객체는 UserDetails 타입이다.
  • GrantAuthority:
    • “ROLE_USER”, “ROLE_ADMIN”등 Principal이 가지고 있는 “권한”을 나타낸다.
    • 인증 이후, 인가 및 권한 확인할 때 이 정보를 참조한다.
  • UserDetails : 애플리케이션이 가지고 있는 유저 정보와 스프링 시큐리티가 사용하는 Authentication 객체 사이의 어댑터이다.
  • UserDetailsService : 유저 정보를 UserDetails 타입으로 가져오는 DAO (Data Access Object) 인터페이스다.





ThreadLocal

  • Java.lang 패키지에서 제공하는 쓰레드 범위 변수이다. -> 즉, 쓰레드 수준의 데이터 저장소이다.
  • ThreadLocal은 Thread마다 고유한 영역을 가지고 있는 곳에 저장된 변수로 각각의 Thread안에서 유효한 변수다.
  • ThreadLocal 은 스프링 시큐리티 외에도 많이 사용되니 잘 알아두는 것이 좋다.
public class AccountContext {

    private static final ThreadLocal<Account> ACCOUNT_THREAD_LOCAL
            = new ThreadLocal<>();

    public static void setAccount(Account account) {
        ACCOUNT_THREAD_LOCAL.set(account);
    }

    public static Account getAccount() {
        return ACCOUNT_THREAD_LOCAL.get();
    }

}
  • 위의 코드는 보면 ThreadLocal 의 set, get 메서드를 구현한 것이다.
  • 위의 메소드를 통해서 요청에 따라 다른 ThreadLocal 을 공유하는 것을 확인할 수 있다.
  • WebMVC 기반으로 프로젝트를 만든다는 가정하에 대부분의 경우, 요청 1개의 Thread 1개가 생성된다.
  • 이때 ThreadLocal을 사용하면 Thread마다 고유한 공간을 만들수가 있고 그곳에 SecurityContext를 저장할 수 있다.
  • 그러나 ThreadLocal만 강제로 사용해야하는 것은 아니며 원하면 SecurityContext 공유 전략을 바꿀 수 있다.
  • Spring Security의 기본적인 Security Context 관리 전략은 ThreadLocal을 사용하는 ThreadLocalSecurityContextHolderStrategy 다.

  • 아래와 같은 전략들을 가진다.
  • MODE_THREADLOCAL
    • ThreadLocalSecurityContextHolderStrategy를 사용한다. -> 디폴트 설정 모드이다.
    • ThreadLocal을 사용하여 같은 Thread안에서 SecurityContext를 공유합니다.
  • MODE_INHERITABLETHREADLOCAL
    • InheritableThreadLocalSecurityContextHolderStrategy를 사용한다. - InheritableThreadLocal을 사용하여 자식 Thread까지도 SecurityContext를 공유한다.
  • MODE_GLOBAL
    • GlobalSecurityContextHolderStrategy를 사용한다.
    • Global로 설정되어 애플리케이션 전체에서 SecurityContext를 공유한다.




REFERENCES

  • 백기선님의 스프링 시큐리티
  • 정수원님의 스프링 시큐리티
  • 안성훈님의 스프링 시큐리티

'Spring Security' 카테고리의 다른 글

CustomAuthenticationProvider  (0) 2022.02.28
AuthenticationManager 와 AuthenticationProvider  (0) 2022.02.28
Security Config  (0) 2022.02.28
SecurityContextHolder 와 FilterChainProxy  (0) 2022.02.28
스프링 시큐리티 아키텍처  (0) 2022.02.28