// ✨ 구현체로 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);
}
}
세션을 만드는 역할을 하는 구현체!
이 부분이 세션 생성 정책
과 연관이 있습니다!
// ✨ `세션 생성 정책`과 연관이 있는 변수
// 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;
}
}
public boolean isAnonymous(Authentication authentication) {
return this.anonymousClass != null && authentication != null ?
this.anonymousClass.isAssignableFrom(authentication.getClass()) : false;
}
세션 정책을 수행하는 친구
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));
}
}
}
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"));
}
}