SecurityContextHolder

Byung Seon Kang·2022년 8월 24일
0

스프링 시큐리티

목록 보기
2/3

공식문서 정리


SecurityContextHolder

  • SecurityContext 를 제공하는 static method(getContext)를 지원한다.

  • 누가 인증되었는가에 대한 자세한 정보(SecurityContext내의 Authentication )를 저장한다.

    • SecurityContextHolder 가 어떻게 있는지는 신경쓰지 않는다. 만약 value를 가지고 있다면, 현재 인증된 유저가 사용한다.
  • 유저가 인증되었음을 나타내는 가장 간단한 방법은 SecurityContextHolder 를 직접 세팅하는것.

    SecurityContext context = SecurityContextHolder.createEmptyContext(); //1)
    Authentication authentication =
        new TestingAuthenticationToken("username", "password", "ROLE_USER"); //2)
    context.setAuthentication(authentication);
    
    SecurityContextHolder.setContext(context); //3)

    1) 빈 SecurityContext 에서 시작한다.

    • 다중 쓰레드에서의 race condition을 회피하기 위해서는 SecurityContextHolder.getContext().setAuthentication(authentication) 을 사용하는 것보다는 새로운 SecurityContext 를 생성하는 것이 좋다.

    2) 새로운 Authentication object를 생성한다.

    • 여기서는 TestingAuthenticationToken 을 사용했지만, 더 일반적인 production 시나리오는 UsernamePasswordAuthenticationToken(userDetails, password, authorities) 를 사용하는 것.

    3) 마지막으로, SecurityContextSecurityContextHolder 에 세팅한다.

    • 이 세팅한 정보를 authorization에 사용하게 될 것.
      → 이 과정들 자동으로 해주는 애가 SecurityContextPersistenceFilter
  • authenticate된 principal에 접근하고 싶으면 SecurityContextHolder 에 접근하면 된다.
    SecurityContext context = SecurityContextHolder.getContext();
    Authentication authentication = context.getAuthentication();
    String username = authentication.getName();
    Object principal = authentication.getPrincipal();
    Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
  • 기본적으로SecurityContextHolder 는 detail들을 담기 위해 ThreadLocal 을 사용함.
    • ThreadLocalSecurityContextHolderStrategy
    • 즉 동일한 쓰레드에서는 SecurityContext 가 method들에 명시적으로, 즉 argument로 passing되지 않더라도 SecurityContext 를 method에서 사용 가능하다는 것.
      • 서버에 여러 클라이언트가 들어와있다고 하자. 그러면 SecurityContext는 누구의 정보를 가져오는지 어떻게 알까?
      • 이걸 ThreadLocal 로 하니까 그냥 편하게 정보를 가져올 수 있는 것.
        • 요청 1개 → thread 1개 → SecurityContext 1개.
    • 만약 현재 principal(주체)의 request가 처리되었을 때 thread를 clear 한다면, 이 ThreadLocal 이 안전한 방법일 것.
      • 스프링 시큐리티에서 FilterChainProxySecurityContext 가 clear 되었음을 보장해주므로.
  • 몇몇 앱들은 ThreadLocal 을 사용하는 것이 적합하지 않을 수 있다.
    • 예를들어 Swing client가 항상 동일한 security context를 사용하기 위해 JVM의 모든 thread를 사용하고싶다고 하는 경우.
    • 이런 경우들에 대해서는 어떻게 context를 저장할지 SecurityContextHolder 설정을 해줘야 한다.
      • 기기에서 독립적으로 돌아가는 standalone application의 경우 SecurityContextHolder.MODE_GLOBAL 전략을 사용할 수 있음.
      • 여러개의 보안상 안전한 thread를 동시에 사용하고 싶은 경우, SecurityContextHolder.MODE_INHERITABLETHREADLOCAL을 사용하면 된다.

SecurityContext

  • 접근 주체와 인증에 대한 정보를 담고 있는 Context
    • 즉, Authentication을 담고 있음.

Authentication

  • principal - 유저를 식별. username과 password를 식별할 때 보통 UserDetail 의 인스턴스가 됨
    • 다시말하면, 유저에 대한 정보
    • 대부분의 경우 PrincipalUserDetails 를 반환.
  • authorities - GrantedAuthority 의 높은 수준의 권한. role, scope등등 있음.
  • credentials - 보통 password. authenticate 되고 나면 보통 clear.
  • 인증이 이루어지면 해당 Authentication이 저장됨.

GrantedAuthority

  • high level permissions the user is granted. A few examples are roles or scopes.
  • Authentication.getAuthorities() 메소드를 통해 획득 가능.
    • 권한이 여러개일 수 있으므로 Colleciton<GrantedAuthority> 형태로 제공됨.
    • 어떤 authorities는 role임. 주로 ROLE_ADMIN 이런거.
    • 스프링 시큐리티의 다른 파트들은 이 authority를 해석하는 능력을 가짐.
      • username/password 기반 인증을 사용하는 경우 GrantedAuthority 는 보통 UserDetailsService에 의해 load된다.

구조 해석


  • 근데 왜 위처럼 여러번 감싸놨을까? 그냥 Authentication 객체로 가지고 있으면 문제가 생기는건가?

SecurityContextHolder 의 코드

  • initializeStrategy

    	private static SecurityContextHolderStrategy strategy;
        ...
      
        private static void initializeStrategy() {
    		if (MODE_PRE_INITIALIZED.equals(strategyName)) {
    			Assert.state(strategy != null, "When using " + MODE_PRE_INITIALIZED
    					+ ", setContextHolderStrategy must be called with the fully constructed strategy");
    			return;
    		}
    		if (!StringUtils.hasText(strategyName)) {
    			// Set default
    			strategyName = MODE_THREADLOCAL;
    		}
    		if (strategyName.equals(MODE_THREADLOCAL)) {
    			strategy = new ThreadLocalSecurityContextHolderStrategy();
    			return;
    		}
    		if (strategyName.equals(MODE_INHERITABLETHREADLOCAL)) {
    			strategy = new InheritableThreadLocalSecurityContextHolderStrategy();
    			return;
    		}
    		if (strategyName.equals(MODE_GLOBAL)) {
    			strategy = new GlobalSecurityContextHolderStrategy();
    			return;
    		}
    		// Try to load a custom strategy
    		try {
    			Class<?> clazz = Class.forName(strategyName);
    			Constructor<?> customStrategy = clazz.getConstructor();
    			strategy = (SecurityContextHolderStrategy) customStrategy.newInstance();
    		}
    		catch (Exception ex) {
    			ReflectionUtils.handleReflectionException(ex);
    		}
    	}
    
      
      ...
    }

    SecurityContextHolderStrategy 타입의 strategy를 초기화해주고 있다.
    ThreadLocal, INHERITABLETHREADLOCAL 등의 조건에 따라 설정해준다.
    default는 ThreadLocal이다.
    즉, SecurityContextHolderStrategy의 Factory 역할을 수행한다.

  • setContext

    public static void setContext(SecurityContext context) {
    	strategy.setContext(context);
    }

    위에서 설정해준 Thread 관련 옵션에 따라 SecurityContext를 지정해준다.
    아래 코드는 ThreadLocalSecurityContextHolderStrategy()의 일부이다.

    private static final ThreadLocal<SecurityContext> contextHolder = new ThreadLocal<>();
    ...
    
    	@Override
    	public void setContext(SecurityContext context) {
    		Assert.notNull(context, "Only non-null SecurityContext instances are permitted");
    		contextHolder.set(context); //ThreadLocalMap에 추가해서 Thread별 관리 들어감.
    	}
  • private static final ThreadLocal<SecurityContext> contextHolder = new ThreadLocal<>(); 는 static으로 지정했는데 thread간 참조문제 없는가?

    • 없다. get set이 모두 currentThread를 참조하기 때문에 static으로 설정해서 단 하나의 contextHolder만 있어도 각 thread의 변수 관리가 가능하다.

SecurityContext 코드

public interface SecurityContext extends Serializable {

	Authentication getAuthentication();

	void setAuthentication(Authentication authentication);
}

정리

  1. Authentication 은 실제 유저의 정보를 담는다.
  2. SecurityContextAuthentication 구현체를 설정된 Thread strategy에 따라 관리.
  3. SecurityContextHolderSecurityContext를 담고 있으며, Thread 관련 strategy를 결정하는 Factory 역할을 한다.

참고

스프링 공식문서

profile
왜 필요한지 질문하기

0개의 댓글