[Spring Security] 인증 상태 영속성
사용자가 인증을 마친 이후 인증을 유지하기 위해 SecurityContextRepository 클래스를 사용한다.
인증 정보와 권한은 SecurityContext에 저장되고, SecurityContext는 다시 HttpSession에 저장돼 요청 간 영속이 이루어진다.
SpringContextRepository 인터페이스를 구현한 구현체로는 두 가지가 있는데, 그 중 하나가 HttpSession에 인증 상태를 저장하는 역할을 수행한다.
인증을 마친 후 요청할 때는 SecurityCOntextHolderFilter에서 컨텍스트를 로드해 인증 상태가 저장되어있는지 확인한다.
public interface SecurityContextRepository {
@Deprecated
SecurityContext loadContext(HttpRequestResponseHolder requestResponseHolder);
default DeferredSecurityContext loadDeferredContext(HttpServletRequest request) {
Supplier<SecurityContext> supplier = () -> loadContext(new HttpRequestResponseHolder(request, null));
return new SupplierDeferredSecurityContext(SingletonSupplier.of(supplier),
SecurityContextHolder.getContextHolderStrategy());
}
void saveContext(SecurityContext context, HttpServletRequest request, HttpServletResponse response);
boolean containsContext(HttpServletRequest request);
}
containsContext : 현재 사용자를 위한 보안 컨텍스트가 저장소에 있는지 조회한다.
saveContext : 인증 완료 시 보안 컨텍스트를 저장한다.
loadDeferredContext : 로딩을 지연시켜 Supplier에 SecurityContext를 저장해두고 필요 시점에 Supplier를 실행시킨다.
HttpSecuritySessionContextRepository는 SecurityContext를 HttpSession에 저장해 영속성을 유지한다.
RequestAttributeSecurityContextRepository는 ServletRequest에 SecurityContext를 저장해 후속 요청 시 영속성을 유지할 수 없다.
NullSecurityContextRepository는 세션을 사용하지 않는 JWT, OAuth2 인증방식에서 사용돼 컨텍스트를 처리하지 않는다.
DelegatingSecurityContextRepository는 초기화 시 기본적으로 설정돼 구현체들을 저장하고 사용한다.
스프링 시큐리티는 필터 기반으로 요청을 처리하고, 각 필터 목록은 FilterChainProxy가 관리한다.
SecurityContextHolderFilter는 Filter목록 중 상단에 위치하며 SecurityContextRepository를 활용해 SecurityContext를 얻고 SecurityContextHolder에 설정하는 역할을 수행한다.
SecurityContext의 저장 여부에 따라서 수행하는 작업이 다르니 해당 필터를 상단에 위치시켜 인증 여부를 처리해야 한다.
이전까지 사용됐던 SecurityContextPersistenceFilter는 saveContext 메서드를 강제로 수행해 세션에 인증 정보를 저장했는데, SecurityContextHolderFilter는 사용자가 명시적으로 saveContext를 실행하도록 한다.
인증 필터에서 인증 처리 시, 필요한 경우만 인증 정보를 세션에 저장해야 한다.
해당 필터는 시큐리티가 무조건 인증 상태를 저장하도록 강제하기보다는, 인증 매커니즘에 따라 다르게 구현할 수 있도록 유연성을 제공한다.
// HttpSessionSecurityContextRepository.java
@Override
public DeferredSecurityContext loadDeferredContext(HttpServletRequest request) {
Supplier<SecurityContext> supplier = () -> readSecurityContextFromSession(request.getSession(false));
return new SupplierDeferredSecurityContext(supplier, this.securityContextHolderStrategy);
}
// AnonymousAuthenticationFilter.java
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
Supplier<SecurityContext> deferredContext = this.securityContextHolderStrategy.getDeferredContext();
this.securityContextHolderStrategy
.setDeferredContext(defaultWithAnonymous((HttpServletRequest) req, deferredContext));
chain.doFilter(req, res);
}
// AuthorizationFilter.java
private Authentication getAuthentication() {
Authentication authentication = this.securityContextHolderStrategy.getContext().getAuthentication();
if (authentication == null) {
throw new AuthenticationCredentialsNotFoundException(
"An Authentication object was not found in the SecurityContext");
}
return authentication;
}
SecurityContext안에 있는 Authentication이 중요하지, SecurityContext 자체는 바로 필요하지 않다.
세션으로부터 가져오는 값을 Supplier로 감싸 로딩을 지연시킨다.
Authentication이 필요한 시점에 Supplier를 사용한다.
익명 사용자는 새로운 객체를 만들어서 SecurityContext에 저장하고, 인증된 사용자는 세션에서 객체를 가져와서 SecurityContext에 저장한다.
그 과정 속에서 객체 생성 / 세션에서 가져오는 작업을 즉시 수행하지 않고 지연시킨다.
.securityContext(securityContext -> securityContext.requireExplicitSave(false))
SecurityConfig에서 해당 설정을 false로 설정 시 SecurityContext의 변경사항을 자동으로 저장한다.
3개의 컴퓨터에서 13months라는 아이디로 로그인했다고 해 보자.
이런 경우, 각 쿠키에 저장되는 SESSIONID는 달라 세션은 3개가 생성되지만 하나의 계정을 공유하게 돼 하나의 계정 대한 세션이 여러 개 생기게 된다.
동시 세션 제어는 사용자가 동시에 여러 세션을 생성하는 작업을 관리하는 전략으로, 사용자 인증 후 활성화된 세션의 수를 설정된 maximumSessions 값과 비교한다.
maximumSessions를 1로 설정했다고 생각해보자.
첫 번째 사용자가 인증을 마친 후, 두 번째 사용자가 인증에 성공 시 첫 번째 사용자의 세션을 만료시키는 방식과
첫 번째 사용자가 인증을 마친 후, 두 번쨰 사용자가 인증을 시도할 때 사용자의 인증을 차단하는 방식을 제공한다.
http.sessionManagement(session -> session
.sessionFixation(sessionFixation -> sessionFixation.changeSessionId())
.invalidSessionUrl("/invalid")
.maximumSessions(1)
.maxSessionsPreventsLogin(true)
.expiredUrl("/expired")
);
invalidSessionUrl : 만료된 세션으로 요청하는 사용자를 리다이렉션할 URL을 지정한다.
maximumSessions : 사용자당 할당하는 최대 세션 수를 제어한다. (기본값은 무제한)
maxSessionsPreventsLogin : true는 maximumSession에 도달할 때 인증을 방지하고 false는 기존 사용자의 세션을 만료시킨다.
expiredUrl : 세션 만료 시 리다이렉션할 URL을 지정하는데, invalidSessionUrl이 우선된다.
sessionFixation 값으로 세션을 고정 보호 전략을 설정해 공격을 방어할 수 있다.
changeSessionId : 기존 세션을 유지하면서 SESSIONID만 바꿔 세션 공격을 방지한다 (기본값)
newSession : 새로운 세션을 생성하고 기존 세션 데이터를 복사하지 않는다 .
none : 기존 세션을 그대로 사용한다.
웬만하면 changeSessionId 기본값을 그대로 사용하는 편이다.
public enum SessionCreationPolicy {
/**
* Always create an {@link HttpSession}
*/
ALWAYS,
/**
* Spring Security will never create an {@link HttpSession}, but will use the
* {@link HttpSession} if it already exists
*/
NEVER,
/**
* Spring Security will only create an {@link HttpSession} if required
*/
IF_REQUIRED,
/**
* Spring Security will never create an {@link HttpSession} and it will never use it
* to obtain the {@link SecurityContext}
*/
STATELESS
}
스프링 시큐리티에서 인증된 사용자에 대한 세션 생성 정책은 ENUM 클래스로 관리한다.
ALWAYS : ForceEagerSessionCreationFilter 클래스를 구성해 인증 여부에 상관없이 항상 세션을 생성한다.
NEVER : 시큐리티가 세션을 생성하지는 않지만, WAS 같은 애플리케이션이 생성한 세션을 사용할 수는 있다.
IF_REQUIRED : 인증이 필요한 경우에만 세션을 생성한다. (기본값)
STATELESS : JWT에서 사용되는 방식으로, 세션을 생성 / 사용하지 않는다.
인증과 관련이 없는 부분에서는 STATELESS 정책을 사용할 때도 세션을 생성할 수 있다.
CSRF 기능을 활성화 한 경우 STATELESS 설정에도 세션을 생성해 CSRF 토큰을 저장하는 것 처럼 인증 외의 부분에서는 STATELESS 설정이 적용되지 않을 수 있다.
요청이 시작된 이후 세션 고정 보호 매커니즘을 활성화하는 등 세션 관련 작업을 수행하는 필터는 SessionManagementFilter이다.
스프링 시큐리티 6부터는 SessionManagementFilter가 기본적으로 설정되지 않아 직접 설정해 줘야 한다.
4개의 구현 클래스가 필터에서 작동한다.
사용자 1과 2가 같은 계정, 다른 컴퓨터로 인증한다고 생각해보자.
사용자1의 인증 요청이 들어오면 AuthenticationFilter가 인증을 수행하고, Session Count를 계산 / SESSIONID를 변경한 후 세션 정보를 등록한다.
이후 사용자2가 인증을 요청하는데 이 때 인증 전략에 따라 인증을 처리한다.
ConcurrentSessionFilter는 사용자의 세션이 만료됐는지 확인한다.
'Spring > Spring Security' 카테고리의 다른 글
[Spring Security] 인가 프로세스 (0) | 2024.06.22 |
---|---|
[Spring Security] 예외 처리 (0) | 2024.06.12 |
[Spring Security] 인증 아키텍처 (0) | 2024.06.08 |
[Spring Security] 인증 메커니즘 (0) | 2024.05.30 |
[Spring Security] 초기화 과정 (0) | 2024.05.27 |
댓글
이 글 공유하기
다른 글
-
[Spring Security] 인가 프로세스
[Spring Security] 인가 프로세스
2024.06.22 -
[Spring Security] 예외 처리
[Spring Security] 예외 처리
2024.06.12 -
[Spring Security] 인증 아키텍처
[Spring Security] 인증 아키텍처
2024.06.08 -
[Spring Security] 인증 메커니즘
[Spring Security] 인증 메커니즘
2024.05.30