Spring/Spring Security

Spring Security(2) - SecurityContext

Security Filter 에서 인증된 정보를 저장하는 SecurityContext 에 대해 알아보겠습니다.

 

SecurityContext 는 접근 주체와 인증에 대한 정보를 담고 있는 Context 입니다.

접근 주체인 Authentication 을 담고 있습니다.

정확히는 SecurityContextHolder 가 SecurityContextHolderStrategy 를 통해 SecurityContext 를 반환할 수 있고, SecurityContext 가 담고 있는 Authentication 정보를 가져올 수 있습니다.

 

 

Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();

위와 같은 형태로 Principal(인증정보를 담는 객체) 를 가져올 수 있습니다.

결국 SecurityContextHolder (정확히는 SecurityContextHolderStrategy) 에 SecurityContext 가 담겨있고, 이 Context 를 통해 우리는 인증정보를 가져올 수 있습니다.

 

SecurityContext 공유전략

SecurityContextHolder 는 Spring boot 2.3.1 을 기준으로 다음의 3가지의 기본 공유전략을 제공합니다. 

1. MODE_THREADLOCAL

ThreadLocalSecurityContextHolderStrategy 클래스를 구현체로 사용하며, ThreadLocal 을 사용하여 SecurityContext 를 공유합니다. ThreadLocal 은 같은 쓰레드 내에서 공유할 수 있는 자원입니다.

아무 설정을 하지 않으면 기본적으로 설정되는 모드 입니다.

 

2. MODE_INHERITABLETHREADLOCAL 

InheritableThreadLocalSecurityContextHolderStrategy 클래스를 구현체로 사용하며, InheritableThreadLocal 을 사용하여 SecurityContext 를 공유합니다. 이는 자식 쓰레드까지 공유할 수 있는 자원입니다. "MODE_INHERITABLETHREADLOCAL" 이름을 넘겨서 설정할 수 있습니다.

설정방법에 대해서는 아래에서 알아보겠습니다.

 

3. MODE_GLOBAL

GlobalSecurityContextHolderStrategy 클래스를 구현체로 사용하며, static 선언하여 SecurityContext 를 저장합니다. 따라서, 해당 JVM 내의 인스턴스들은 모두 공유할 수 있습니다.

 

공유전략 설정

공유전략을 설정하는 방법에 대해 알아보겠습니다.

1. 시스템 변수 설정

인텔리제이에서 위와같이 설정가능
위의 설정대로 GlobalSecurityContextHolderStrategy 가 설정되었다.
커맨드라인에서 실행할때 -D로 파라미터를 넘길 수 있다.

2. SecurityContextHolder 의 static method 로 설정

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
  @Override
  protected void configure(HttpSecurity http) throws Exception {
      // 시큐리티 홀더의 공유 전략 설정 - 쓰레드가 생성하는 하위 쓰레드까지 자원공유
      SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);
  }
 }

SecurityContextHolder 의 static method 인 setStragegyName 으로 전략의 이름을 넘겨 설정할 수 있다.

1에서 본 SYSTEM property 를 이용하는 방법은 SecurityContextHolder 가 로딩되는 시점(static init block)에 전략을 설정하고, 위의 방법은 Security 설정정보를 읽는 과정에서 전략을 설정하기 때문에 둘다 설정하였을 경우 2의 방법으로 설정된다.

 

2번의 방법으로 공유전략을 중간에 바꿀 수도 있지만,  해당 메소드의 주석에도 설명되어 있듯이 중간에 전략을 바꾸는 것은 의도한 대로 동작하지 않을 가능성이 크기 때문에 지양해야 합니다.

 

커스텀 공유 전략 추가

커스텀하게 구현한 공유 전략을 추가할수도 있습니다. 

package me.study.securitystudy.common;

import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolderStrategy;
import org.springframework.security.core.context.SecurityContextImpl;
import org.springframework.util.Assert;

public final class CustomSecurityContextHolderStrategy implements SecurityContextHolderStrategy {

    private static final ThreadLocal<SecurityContext> contextHolder = new InheritableThreadLocal<>();

    public void clearContext() {
        contextHolder.remove();
    }

    public SecurityContext getContext() {
        System.out.println("Custom strategy start");
        SecurityContext ctx = contextHolder.get();

        if (ctx == null) {
            ctx = createEmptyContext();
            contextHolder.set(ctx);
        }
        System.out.println("Custom strategy end : " + ctx.getClass());
        return ctx;
    }

    public void setContext(SecurityContext context) {
        Assert.notNull(context, "Only non-null SecurityContext instances are permitted");
        contextHolder.set(context);
    }

    public SecurityContext createEmptyContext() {
        return new SecurityContextImpl();
    }
}

구현한 내용은 getContext 가 호출되면 로그를 찍도록 해보았습니다.

SecurityContextHolder.setStrategyName("me.study.securitystudy.common.CustomSecurityContextHolderStrategy");

패키지 경로까지 풀네임을 적어주어야 합니다. 그리고, 애플리케이션을 실행하여 getContext를 가져오도록 http://localhost:8080 으로 요청을 보내면

로그가 제대로 찍히는 걸 확인할 수 있습니다. 사실 위와 같이 커스텀한 전략을 사용할 경우가 있는 지는 모르겠습니다.

 

이상으로 SecurityContext 에 대해 알아보았습니다. 내용은 거의 SecurityContext 를 저장하는 방법에 대해 얘기된 거 같습니다. 다음 포스팅에서는 SecurityContext 가 담고 있는 Authentication 에 대해 알아보겠습니다.

 

참고자료

https://www.inflearn.com/course/%EB%B0%B1%EA%B8%B0%EC%84%A0-%EC%8A%A4%ED%94%84%EB%A7%81-%EC%8B%9C%ED%81%90%EB%A6%AC%ED%8B%B0