최근까지 하던 업무 중 동시접속에 대한 제어 기능 추가가 있었다.
스프링 시큐리티인듯 아닌듯한 프레임워크이기에 스프링 시큐리티 필터와 직접 SessionRegistry를 이용하여 세션 정보를 이용하는 방법 두 가지를 모두 이용하였다.
** 스프링 부트 프로젝트도 아니고 시큐리티 관련 설정을 모두 xml 파일에서 했기에 그에 맞춰 정리해본다.
먼저 📄 web.xml에 세션 처리 관련 리스너를 추가한다.
<listener>
<listener-class>
org.springframework.security.web.session.HttpSessionEventPublisher
</listener-class>
</listener>
다음으로 security 관련 설정 xml 파일에 아래 내용을 추가하면 된다고는 하는데
<http>
...
<session-management>
<concurrency-control max-sessions="1" error-if-maximum-exceeded="true" />
</session-management>
...
</http>
나의 경우 자사 프레임워크의 특징인지 뭔지 안먹혀서 이 방법은 안썼다.
그 대신 동시성 제어 기능을 제공하는 SessionAuthenticationStrategy를 추가하였다.
* SessionAuthenticationStrategy는 인터페이스이고 이를 구현한 것이 CompositeSessionAuthenticationStrategy
<http>
<!-- 여러 커스텀 필터들 뒤 마지막에 추가 -->
<session-management session-authentication-strategy-ref="sas"/>
</http>
<!-- 커스텀 필터 -->
<beans:bean id="myAuthFilter" class="org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter">
<!-- SessionAuthenticationStrategy 추가 -->
<beans:property name="sessionAuthenticationStrategy" ref="sas" />
...
<beans:property name="authenticationManager" ref="authenticationManager" />
</beans:bean>
<beans:bean id="sas" class="org.springframework.security.web.authentication.session.CompositeSessionAuthenticationStrategy">
<beans:constructor-arg>
<beans:list>
<beans:bean class="org.springframework.security.web.authentication.session.ConcurrentSessionControlAuthenticationStrategy">
<beans:constructor-arg ref="sessionRegistry"/>
<beans:property name="maximumSessions" value="1" />
<beans:property name="exceptionIfMaximumExceeded" value="true" />
</beans:bean>
<beans:bean class="org.springframework.security.web.authentication.session.SessionFixationProtectionStrategy">
</beans:bean>
<beans:bean class="org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy">
<beans:constructor-arg ref="sessionRegistry"/>
</beans:bean>
</beans:list>
</beans:constructor-arg>
</beans:bean>
<beans:bean id="sessionRegistry" class="org.springframework.security.core.session.SessionRegistryImpl" />
exceptionIfMaximumExceeded 옵션을 통해 간단하게 두번째 로그인을 차단할 수 있지만 내 요구사항의 경우 이전 로그인을 만료시켜야했다.
따라서 앞선 설명처럼 ConcurrentSessionFilter도 추가해주었다.
<http>
<!-- 다른 커스텀 필터들 ... -->
<custom-filter position="CONCURRENT_SESSION_FILTER" ref="concurrencyFilter" />
<session-management session-authentication-strategy-ref="sas"/>
</http>
...
<!-- ConcurrentySessionFilter 추가 -->
<beans:bean id="concurrencyFilter" class="org.springframework.security.web.session.ConcurrentSessionFilter">
<beans:constructor-arg name="sessionRegistry" ref="sessionRegistry" />
<beans:constructor-arg name="sessionInformationExpiredStrategy" ref="CustomSessionInfoExpiredStrategy" />
</beans:bean>
<beans:bean id="CustomSessionInfoExpiredStrategy" class="{class경로}.CustomSessionInfoExpiredStrategy"/>
해당 클래스에서는 expired 여부가 true인 세션에 대해 처리 방안을 구현한다.
public class CustomSessionInfoExpiredStrategy implements SessionInformationExpiredStrategy {
@Override
public void onExpiredSessionDetected(SessionInformationExpiredEvent event) throws IOException, ServletException {
// 만료된 세션 처리
}
}
** 여기서 주의해야할 점은 빈 속성으로 expiredUrl이 아닌 sessioinInformatioinExpriedStrategy를 지정해줘야한다는 것!!
[Spring Security 4.2 sessionInformationExpiredStrategy 사용] https://underbell.tistory.com/90
간혹 구글링하다보면 ConcurrentSessionFilter 주입하는 부분이 아래처럼 작성된 코드들이 있다.
<beans:bean id="concurrencyFilter" class="org.springframework.security.web.session.ConcurrentSessionFilter">
<beans:property name="sessionRegistry" ref="sessionRegistry" />
<beans:property name="expiredUrl" value="/logout" />
</beans:bean>
하지만 이렇게 작성하면 expiredUrl이 읽히지않아 NullPointerException으로 오류뜨면서 이전 사용자가 제대로 로그아웃되지 않는다ㅠ(이 에러 원인 찾는데 거의 며칠을 보낸듯)
그리고 저 bean 주입할 때 property가 아니라 construtor-arg로 해라 뭐 이런글도 봤던거같은데 기억이 잘 안난다..(찾아보고 추가예정)
[다양한 의존객체 주입 (constructor-arg, property)] https://junior-datalist.tistory.com/36
뭐 이런저런 우여곡절끝에 동시접속 차단 요구사항 해결 👍🏻
[토리맘의 한글라이즈 프로젝트 | Spring Security Authentication] https://godekdls.github.io/Spring%20Security/authentication/
[SPRING] ServletRequestEvent To Session | 커스텀ServletRequestListener에서 Session 이용하기 (0) | 2022.12.27 |
---|---|
[SPRING SECURITY] SessionRegistry 이용하기 (1) | 2022.11.20 |
[SPRING SECURITY] 세션관리를 위한 SessionManagementFilter와 ConcurrentSessionFilter (0) | 2022.11.20 |
[SPRING BOOT] 2521 과몰입러의 스프링부트 웹소켓 채팅 서비스 만들기(2) 화면구성과 JS (0) | 2022.03.15 |
[SPRING BOOT] 2521 과몰입러의 스프링부트 웹소켓 채팅 서비스 만들기(1) 자바소스 (0) | 2022.03.14 |
댓글 영역