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 |