Java/Spring

Spring Security SessionManagementFilter, ConcurrentSessionFilter(동시세션제어)

B.OCK 2024. 9. 6. 11:06
반응형

SessionManagementFilter

세션 관리를 위한 필터로 Spring Security 에서 4가지 기능을 지원한다.

 

  1. 세션관리 : 인증 후 사용자의 세션 관리(등록, 조회, 삭제)
  2. 동시 세션 제어 : 동일 계정에 대한 세션 관리
  3. 세션 고정 보호 : 인증 후 세션 쿠키를 재발급하여 세션 쿠키 조작 공격을 방지
  4. 세션 생성 정책 : Always, If_Required, Never, Stateless

 

여기서 동시세션제어는 ConcurrentSessionFilter 가 담당한다.

 

 

ConcurrentSessionFilter

ConCurrentSessionFilter 는 SessionRegistry 를 이용하여 사용자의 로그인 동시세션 제어를 한다.

사용자 요청이 들어올 때마다 매번 세션이 만료(expired) 되었는지 체크하고 만료되었다면

해당 요청자를 로그아웃 시킨다.

 

로그아웃은 LogoutHandler 를 통해 인증객체가 저장되어 있는 SecurityContext 를 불러와

null 처리 한 후 SecurityContextRepository 에 저장하여 인증정보를 제거해버린다.

 

아래는 ConCurrentSessionFilter 의 doFilter 메서드이다.

public class ConcurrentSessionFilter extends GenericFilterBean {
	private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder
		.getContextHolderStrategy();
        
    private final SessionRegistry sessionRegistry;
    private LogoutHandler handlers = new CompositeLogoutHandler(new SecurityContextLogoutHandler());
    
    public ConcurrentSessionFilter(SessionRegistry sessionRegistry) {
		Assert.notNull(sessionRegistry, "SessionRegistry required");
		this.sessionRegistry = sessionRegistry;
		this.sessionInformationExpiredStrategy = new ResponseBodySessionInformationExpiredStrategy();
	}
    
    ...
    
    private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
			throws IOException, ServletException {
		HttpSession session = request.getSession(false);
		if (session != null) {
			SessionInformation info = this.sessionRegistry.getSessionInformation(session.getId());
			if (info != null) {
				if (info.isExpired()) {
					// Expired - abort processing
					this.logger.debug(LogMessage
						.of(() -> "Requested session ID " + request.getRequestedSessionId() + " has expired."));
					doLogout(request, response);
					this.sessionInformationExpiredStrategy
						.onExpiredSessionDetected(new SessionInformationExpiredEvent(info, request, response));
					return;
				}
				// Non-expired - update last request date/time
				this.sessionRegistry.refreshLastRequest(info.getSessionId());
			}
		}
		chain.doFilter(request, response);
	}
}

 

 

SessionRegistry 를 이용하여 SessionRegistryImpl 에서 Session 정보를 불러온다.

 

불러온 Session 정보가 만료(isExpired) 되었는지 확인하고 만료되었다면 doLogout() 메서드를 통해

로그아웃 시킨다.

 

logout 메서드는 SecurityContextLogoutHandler 클래스에 존재한다.

 

public class SecurityContextLogoutHandler implements LogoutHandler {
    @Override
	public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
		Assert.notNull(request, "HttpServletRequest required");
		if (this.invalidateHttpSession) {
			HttpSession session = request.getSession(false);
			if (session != null) {
				session.invalidate();
				if (this.logger.isDebugEnabled()) {
					this.logger.debug(LogMessage.format("Invalidated session %s", session.getId()));
				}
			}
		}
		SecurityContext context = this.securityContextHolderStrategy.getContext();
		this.securityContextHolderStrategy.clearContext();
		if (this.clearAuthentication) {
			context.setAuthentication(null);
		}
		SecurityContext emptyContext = this.securityContextHolderStrategy.createEmptyContext();
		this.securityContextRepository.saveContext(emptyContext, request, response);
	}
 }

 

SecurityContext 를 불러와 인증객체를 null 처리 하고 SecurityContextRepository 에 빈 SecurityContext 를 넣어

로그아웃 처리한다.

 

ConcurrentSessionFilter Flow

 

  1. 사용자 인증 요청이 들어온다.
  2. SessionManagementFilter 에서 SessionAuthenticationStrategy (세션 관리 전략) 중
    ConcurrentSessionControlAuthenticationStrategy 에 의해 최대 세션 수를 체크하고
    최대 세션수를 초과 했을때 이전 사용자 또는 로그인 한 사용자의 session 을 만료시킨다.
  3. 이전 사용자 또는 만료된 사용자가 요청했을 때 ConcurrentSessionFilter 에 의해 세션 만료를 검사하고
    만료 시 로그아웃 시킨다.

 

 

반응형