이 시리즈에 나오는 모든 내용은 인프런 인터넷 강의 - 스프링 시큐리티 - Spring Boot 기반으로 개발하는 Spring Security - 에서 기반된 것입니다. 그리고 여기서 인용되는 PPT 이미지 또한 모두 해당 강의에서 가져왔음을 알립니다.
그림이 너무 작게 보이면 클릭해서 크게 보시기 바랍니다.
이전 글에서 동시 세션 제어, 세션 고정 보호, 세션 생성 정책에 대해서 가볍게
알아봤다. 이번에는 그게 정말 가능하게 해주는 SessionManagementFilter 를 알아보자.
저번 글에서 배운 것들을 다 한다고 보면 된다.
1. 세션관리
:인증 이후 사용자의 세션정보를 메모리에 등록/조회/삭제(=이력관리),
이 기능을 사용해서 동시세션제어의 세션 수 체크
2. 동시 세션 제어
: 동일 계정 세션 수 제한
3. 세션고정보호
: 매 인증마다 새로운 세션쿠키 발급
4. 세션 생성 정책
: Always
, If_Required
, Never
, Stateless
에 따라 다른 방식으로 세션 생성
SessionManagementFilter
는 ConcurrentSessionFilter
와 협력하여
동시 세션 제어를 수행한다.
이 필터는 매 요청마다 사용자의 세션 만료 여부를 체크하고
세션이 만료되었을 경우 즉시 만료 처리한다.
SessionManagementFilter
는 ConcurrentSessionFilter
의 연계 동작을 알아보자.
실제 코드상의 프로세스는 아래 그림과 같다.
위 그림의 흐림대로 정말 진행되는지 눈으로 확인해보자.
일단 서로 다른 종류의 브라우저 창을 하나씩 키자.
나는 왼쪽에는 Chrome, 오른쪽에는 FireFox 브라우저를 각각 하나씩 띄웠다.
이 상태에서 먼저 FireFox(오른쪽 브라우저)로 로그인을 시도한다.
3-1. AbstractAuthenticationProcessingFilter
그러면 먼저 인증과 관련된 작업을 돕는 AbstractAuthenticationProcessingFilter
필터가
호출되고 해당 필터에서 세션과 관련된 작업을 위한 메소드를 호출하는 것을 확인할 수 있다.
3-2. CompositeSessionAuthenticationStrategy
인증 필터에서 호출된 메소드는 CompositeSessionAuthenticationStrategy
의 메소드이다.
그리고 메소드 내용은 위 그림과 같다.
for 문을 돌면서 SessionAuthenticationStrategy 에게 인증 작업을 위임하는 코드임을 금방 알 수 있다.
그런데 CompositeSessionAuthenticationStrategy
객체의 역할은 뭘까?
위 그림은 CompositeSessionAuthenticationStrategy
의 java document
이다.
대충 읽어보면 알겠지만, Session 과 관련된 Strategy(전략)들을 모으고, 해당 전략들에게
작업을 순차적으로 위임해주는 객체이다.
참고: 실제 디버깅 포인트를 잡아보면?
실제 디버그 포인트를 잡으면 java doc에서 말한 3가지 Strategy(전략)이 보인다.
지금부터는 저 전략들이 수행되는 과정을 따라가보자.
(CsrfAuthenticationStrategy 제외!)
3-3. ConcurrentSessionControlAuthenticationStrategy
CompositeSessionAuthenticationStrategy
의 for 문에서 첫번째로 사용하는
전략인 ConcurrentSessionControlAuthenticationStrategy
가 수행된다.
코드를 가볍게 보면, sessionRegistry 라는 곳에서 현재 사용자의 세션 개수를 세오고,
그 세션의 숫자가 allowedSessions
를 넘으면 어떤 처리를 하는 것을 알 수 있다.
하지만 지금은 첫 로그인이라서 별문제 없이 그냥 메소드가 return 된다.
3-4. AbstractSessionFixationProtectionStrategy (implements SessionAuthenticationStrategy)
이후로 CompositeSessionAuthenticationStrategy
의 for 문을 돌면서 다음 전략인
AbstractSessionFixationProtectionStrategy
가 동작한다.
applySessionFixation(request)
메소드의 내용물은 위와 같다.
세션 아이디를 바꾸는 것을 확인할 수 있다. 이렇게 함으로써 세션고정보호가 되는 것이다.
3-5. RegisterSessionAuthenticationStrategy
위 메소드는 SessionRegistryImpl
인스턴스의 registerNewSession 을 호출한다.
그리고 새로운 세션을 저장하는 것을 확인할 수 있다.
3-6. 다른 브라우저에서 사용자 로그인 시도
이번에는 Chrome 브라우저에서 같은 아이디로 로그인을 해보자.
3-7. 다시 ConcurrentSessionControlAuthenticationStrategy
이전과는 다르게 ConcurrentSessionControlAuthenticationStrategy
에서
allowableSessionsExceeded(sessions, allowedSessions, this.sessionRegistry);
메소드를 호출한다.
이건 if(sessionCount < allowedSessions)
분기문을 통과해서 그런 것이다.
sessionCount 는 이전에 로그인을 해서 1개이고,
allowedSessions 는 우리가 이전에 한 설정에서 http().sessionManagement().maximumSessions(1)
때문에 1개로 고정되어 있다.
해당 메소드의 내용은 위와 같고, this.exceptionIfMaximumExceeded=true
여서
SessionAuthenticationException
예외를 던지게 된다.
참고로 exceptionIfMaximumExceeded
는 우리가 이전에 한 설정
http().sessionManagement().maxSessionPreventsLogin(true)
덕분에 이렇게 동작하는 것이다.
만약에 .maxSessionPreventsLogin(false)
로 지정하면 처음 들어왔던 사용자의 세션을 만료시킨다.
==> session.expireNow()
일단 지금은 예외가 던져지는 경우를 계속 추적해보겠다.
3-8. 인증필터에서 try-catch 및 unsuccessfulAuthentication 메소드 호출
이전 인증 필터 게시물에서 설명했으니 이번엔 길게 설명 X.
3-9. 두번째 사용자 결국 로그인 실패하고 로그인 페이지로 튕겨냄
이전에 본 3-7
과정의 코드에 디버깅 포인트를 다시 잡고 Spring Security 설정을 위처럼 바꿔주자.
그 다음에 이전처럼 로그인 FireFox 로그인을 성공시키고, Chrome 사용자를 로그인 시켜보자
3-10. ConcurrentSessionControlAuthenticationStrategy
Chrome 사용자가 로그인을 시키면 이전 FireFox 로 로그인한 세션은 expireNow() 에 의해서 만료처리된다.
이후에 다시 FireFox 로 어떤 경로에 접근하려하면...
3-11. ConcurrentSessionFilter
ConcurrentSessionFilter 는 세션 만료를 체킹하는데, 지금은 FireFox 세션이 만료되었으므로
위처럼 info.isExpired
분기문 내부로 들어간다.
doLogout
: 로그아웃 처리를 한다(= 로그아웃 핸들러들이 동작한다)
onExpiredSessionDetected
는 세션 Expired 처리 전략을 수행한다.
아무것도 세팅해주지 않았다면 기본적으로 경고 문구를 flush 해버리고 끝이다.
참고: 만약에 Expired 처리를 다르게 하고 싶다면?
.sessionManagement() .maximumSessions(1) .maxSessionsPreventsLogin(false) .expiredSessionStrategy(new SessionInformationExpiredStrategy() { @Override public void onExpiredSessionDetected(SessionInformationExpiredEvent event) throws IOException, ServletException { HttpServletResponse response = event.getResponse(); response.sendRedirect("/sessionOver"); } });
추가적으로 이렇게 페이지 redirect 를 하려면 spring security 로
저 경로(/sessionOver
)를 permitAll 해줘야 정상적으로 동작한다!!