SessionManagementFilter

midas·2022년 5월 20일
1

SessionManagementFilter란?

  1. 세션 관리
  2. 동시적 세션 제어 : 동일 계정 - 접속 허용 최대 세션수 제한
  3. 세션 고정 보호 : 인증할 때마다 세션 쿠키 새로 발급 → 공격자 쿠키 조작 방지
  4. 세션 생성 정책
    • Always : 항상 세션 생성
    • If_Required : 필요시 생성 [Default]
    • Never : 생성 X, 이미 존재하면 사용!
    • Stateless : 생성 X, 존재해도 사용 X!

코드 분석!

// ✨ 구현체로 HttpSessionSecurityContextRepository가 있습니다.
private final SecurityContextRepository securityContextRepository;

// ✨ 여기서는 anonymous 계정인지 확인용
private AuthenticationTrustResolver trustResolver;

// ✨ 세션 정책들을 수행하는 친구 → CompositeSessionAuthenticationStrategy가 구현체로 들어옴
private SessionAuthenticationStrategy sessionAuthenticationStrategy;


private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
  if (request.getAttribute("__spring_security_session_mgmt_filter_applied") != null) {
    chain.doFilter(request, response);
  } else {
    request.setAttribute("__spring_security_session_mgmt_filter_applied", Boolean.TRUE);

    // ✨ 세션이 존재하지 않는다면!
    if (!this.securityContextRepository.containsContext(request)) {

      // ✨ SecurityContext에서 authentication 정보를 들고오고
      Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

      // ✨ authentication가 null 아니고 Anonymous 계정도 아니어야 한다!
      if (authentication != null && !this.trustResolver.isAnonymous(authentication)) {

        // ✨ 먼저 세션 정책들을 수행합니다.
        try {
          this.sessionAuthenticationStrategy.onAuthentication(authentication, request, response);
        } catch (SessionAuthenticationException var6) {
          this.logger.debug("SessionAuthenticationStrategy rejected the authentication object", var6);
          SecurityContextHolder.clearContext();
          this.failureHandler.onAuthenticationFailure(request, response, var6);
          return;
        }

        // ✨ 세션정책에 어긋나지 않는다면! 세션을 저장하게 됩니다!
        this.securityContextRepository.saveContext(SecurityContextHolder.getContext(), request, response);
      } else if (request.getRequestedSessionId() != null && !request.isRequestedSessionIdValid()) {
        if (this.logger.isDebugEnabled()) {
          this.logger.debug(LogMessage.format("Request requested invalid session id %s", request.getRequestedSessionId()));
        }

        if (this.invalidSessionStrategy != null) {
          this.invalidSessionStrategy.onInvalidSessionDetected(request, response);
          return;
        }
      }
    }

    chain.doFilter(request, response);
  }
}

HttpSessionSecurityContextRepository securityContextRepository

세션을 만드는 역할을 하는 구현체!
이 부분이 세션 생성 정책과 연관이 있습니다!

// ✨ `세션 생성 정책`과 연관이 있는 변수
// STATELESS, NEVER의 경우에는 이 변수가 false 처리 되어 Session이 설정되지 않음
private boolean allowSessionCreation = true;

// ✨ Session 필터에서 사용되는 메서드
public boolean containsContext(HttpServletRequest request) {
  HttpSession session = request.getSession(false);
  if (session == null) {
    return false;
  } else {
    return session.getAttribute(this.springSecurityContextKey) != null;
  }
}

AuthenticationTrustResolverImpl → AuthenticationTrustResolver trustResolver

public boolean isAnonymous(Authentication authentication) {
  return this.anonymousClass != null && authentication != null ?
    this.anonymousClass.isAssignableFrom(authentication.getClass()) : false;
}

CompositeSessionAuthenticationStrategy → SessionAuthenticationStrategy

세션 정책을 수행하는 친구
Composite 이라는 말이 합쳐놓은 것이니 여러가지 정책들을 수행합니다.

delegateStrategies 여기에는 총 4개가 있습니다. 🤔

private final List<SessionAuthenticationStrategy> delegateStrategies;

public void onAuthentication(Authentication authentication, HttpServletRequest request, HttpServletResponse response) throws SessionAuthenticationException {
  int currentPosition = 0;
  int size = this.delegateStrategies.size();

  SessionAuthenticationStrategy delegate;

  // ✨ 결국 4개의 객체들을 각각 onAuthentication() 메서드를 여기서 실행하게 됩니다.
  // 그 중에서 ConcurrentSessionControlAuthenticationStrategy는
  //  매 요청마다 현재 사용자의 세션 만료 여부를 체크하고,
  //  세션이 만료로 설정되었을 경우에는 즉시 만료처리
  for(Iterator var6 = this.delegateStrategies.iterator(); var6.hasNext(); delegate.onAuthentication(authentication, request, response)) {
    delegate = (SessionAuthenticationStrategy)var6.next();
    if (this.logger.isTraceEnabled()) {
      Log var10000 = this.logger;
      String var10002 = delegate.getClass().getSimpleName();
      ++currentPosition;
      var10000.trace(LogMessage.format("Preparing session with %s (%d/%d)", var10002, currentPosition, size));
    }
  }

}

ConcurrentSessionControlAuthenticationStrategy

public void onAuthentication(Authentication authentication, HttpServletRequest request, HttpServletResponse response) {
  int allowedSessions = this.getMaximumSessionsForThisUser(authentication);
  if (allowedSessions != -1) {
    List<SessionInformation> sessions = this.sessionRegistry.getAllSessions(authentication.getPrincipal(), false);
    int sessionCount = sessions.size();

    // ✨세션 Count가 초과 되었다면?
    if (sessionCount >= allowedSessions) {

      // ✨ 하지만 맥시멈과 동일하고, 요청 세션 == 세션들 중 하나 → PASS
      if (sessionCount == allowedSessions) {
        HttpSession session = request.getSession(false);
        if (session != null) {
          Iterator var8 = sessions.iterator();

          while(var8.hasNext()) {
            SessionInformation si = (SessionInformation)var8.next();
            if (si.getSessionId().equals(session.getId())) {
              return;
            }
          }
        }
      }

      // ✨ 자 만료를 시키러 가봅시다!
      this.allowableSessionsExceeded(sessions, allowedSessions, this.sessionRegistry);
    }
  }
}

// ✨ 초과된 세션 만료 시키기!
protected void allowableSessionsExceeded(List<SessionInformation> sessions, int allowableSessions, SessionRegistry registry) throws SessionAuthenticationException {
  if (!this.exceptionIfMaximumExceeded && sessions != null) {
    // ✨ 초과된 세션들을 가져오기 위해서 마지막에 요청된 녀석들을 먼저 나오도록 정렬한다!
    sessions.sort(Comparator.comparing(SessionInformation::getLastRequest));
    int maximumSessionsExceededBy = sessions.size() - allowableSessions + 1;
    List<SessionInformation> sessionsToBeExpired = sessions.subList(0, maximumSessionsExceededBy);
    Iterator var6 = sessionsToBeExpired.iterator();

    while(var6.hasNext()) {
      SessionInformation session = (SessionInformation)var6.next();
      session.expireNow(); // ✨ 만료 가즈아!
    }

  } else {
    throw new SessionAuthenticationException(this.messages.getMessage("ConcurrentSessionControlAuthenticationStrategy.exceededAllowed", new Object[]{allowableSessions}, "Maximum sessions of {0} for this principal exceeded"));
  }
}
profile
BackEnd 개발 일기

0개의 댓글