서론
시큐리티를 공부하다가 문득 이런 생각이 들었다.
SecurityContextHolder, SecurityContext, Authentication 는 왜 나눠놓은걸까?
그냥 하나로 퉁치면 되는거 아닐까?
그에 대한 고민과 해답을 본 포스팅에 적어보고자 한다.
참고
본론
또 너냐.. "객체지향 프로그래밍"
결론부터 말하자면 책임과 역할의 분리다. 캡슐화의 특징이라고 볼 수 있다.
시큐리티 공식문서엔 이와 같이 표현되어있다.
일단 각각의 정의에 대해 정리해보자.
SecurityContextHolder
SecurityContext의 컨테이너 역할을 수행한다.
The SecurityContextHolder is where Spring Security stores the details of who is authenticated.
Spring Security does not care how the SecurityContextHolder is populated. If it contains a value, it is used as the currently authenticated user.
SecurityContextHolder는 Spring Security가 인증된 사람에 대한 세부 정보를 저장하는 곳입니다. Spring Security는 SecurityContextHolder가 어떻게 채워지는지 신경 쓰지 않습니다. 값이 포함되어 있으면 현재 인증된 사용자로 사용됩니다.
즉, SecurityContextHolder는 SecurityContext를 핸들링하는 목적으로만 사용되고(일종의 DTO 느낌과 비슷하다고 생각하자.)
내부에서 어떤 일이 처리되었는지는 신경쓰지 않기 위해 만들어진 객체이다.
SecurityContext
Authentication을 가지고 있는 인터페이스, SecurityContextHolder에서 가져온다.
The SecurityContext is obtained from the SecurityContextHolder. The SecurityContext contains an Authentication object.
SecurityContext는 SecurityContextHolder에서 가져옵니다. SecurityContext에는 인증 개체가 포함되어 있습니다.
이 친구는 Authentication을 get하고 set하는 역할을 가지고있다. 실제로 코드를 뜯어보면 다음과 같다.
public interface SecurityContext extends Serializable {
Authentication getAuthentication();
void setAuthentication(Authentication authentication);
}
별거 없다. 그냥 Getter/Setter 느낌이다.
Authentication
사용자의 인증 정보를 담고 있는 객체이다.
사실 이 Authentication이 시큐리티의 핵심이라고 볼 수 있다. 한 번 Authentication 객체를 가져오면, 그 후의 비즈니스 로직은 Authentication에 포함된 유저 정보를 핸들링하기 때문이다.
Authentication이 가지고 있는 역할은 다음과 같다.
The Authentication interface serves two main purposes within Spring Security:
- An input to AuthenticationManager to provide the credentials a user has provided to authenticate. When used in this scenario, isAuthenticated() returns false.
- Represent the currently authenticated user. You can obtain the current Authentication from the SecurityContext.
인증 인터페이스는 Spring Security 내에서 두 가지 주요 목적을 제공합니다.
- 사용자가 인증을 위해 제공한 자격 증명을 제공하기 위한 AuthenticationManager에 대한 입력입니다. 이 시나리오에서 사용되면 isAuthenticated()는 false를 반환합니다.
- 현재 인증된 사용자를 나타냅니다. SecurityContext에서 현재 인증을 얻을 수 있습니다.
즉, AuthenticationManager와의 통신과 사용자 정보를 담고있는 역할을 한다.
그렇다면 Authentication이 SecurityContextHolder에 어떻게 들어가는걸까?
그런데 생각해보면, 내가 개발을 할 때 SecurityContextHolder를 통해 비즈니스 로직을 수행하진 않았다.@Override
public Authentication getAuthenticationFromRequest(HttpServletRequest request) {
var token = getTokenFromRequest(request, TokenErrorCode.INVALID_ACCESS_TOKEN);
return getAuthenticationFromAccessToken(token);
}
직접적으로 Authentication으로 데이터를 핸들링했었는데, SecurityContextHolder에 어떻게 꽂히게 되는걸까?
이에 대한 해답도 공식문서에서 찾을 수 있었다.
- SecurityContextHolder는 애플리케이션 실행 시점에 자동으로 초기화된다. 기본적으로 스레드당 하나의 SecurityContext 인스턴스를 관리하는 ThreadLocal 방식을 사용한다.
- 이후, 비즈니스 로직을 통해 Authentication이 생성되게 되면, 현재 SecurityContext에 Authentication 객체를 설정한다.
- 만약 현재 스레드의 SecurityContext가 존재하지 않는 경우, 새로운 SecurityContext 인스턴스가 생성되고, 여기에 Authentication이 꽂히게 된다.
결론
결국 이렇게 세 가지 계층으로 나눈 이유는 역할과 책임의 분리에 있다는 것을 알았다.
오루리 개발 당시, Authentication만 있으면 되지않나? 라는 생각을 했었는데, 복습을 하면서 이런 내용도 되짚을 수 있었다.
개발 당시엔 Authentication만 가지고 작업을 했던 것 같은데, 실제로는 SecurityContextHolder를 통해서 가져와야 하는게 아닌가 싶다.. 다음 프로젝트에 올바른 방향으로 적용해보도록 하자.