사이버 보안 관련으로, 다중 접속 시 기존 로그인 계정을 자동으로 로그아웃 시키고 알림을 표시해주는 기능을 개발하게 되었다.
현재 프로젝트는 스프링 시큐리티를 사용 중이어서, 스프링 시큐리티에서 제공해주는 세션 관리 메서드를 활용해서 개발을 진행했다.
세션이 만료되었을 때 처리 방법을 정의한 클래스를 만들어 적용하여 기능을 구현해보았다.
기존 로그인 세션을 자동으로 로그아웃 시키는 것은 금방 구현했는데, 기존 로그인 사용자에게 알림을 어떻게 보내야 될 지 고민을 많이 했다.
나름대로 풀어낸 고민의 결과를 지금부터 기록해본다.
SessionInformationExpiredStrategy
를 구현한 커스텀 클래스에 정의한다.DUPLICATE_LOGIN = true
를 추가한 후, login 페이지로 이동한다.@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// ...
http.sessionManagement()
.sessionFixation().changeSessionId()
.maximumSessions(1)
.expiredSessionStrategy(customSessionExpiredStrategy)
.maxSessionsPreventsLogin(false)
.sessionRegistry(sessionRegistry());
}
// ...
@Bean
public SessionRegistry sessionRegistry() {
return new SessionRegistryImpl();
}
@Bean
public static ServletListenerRegistrationBean<HttpSessionEventPublisher> httpSessionEventPublisher() {
return new ServletListenerRegistrationBean<>(new HttpSessionEventPublisher());
}
}
http.sessionManagement()
설정 추가sessionManagement()
: Spring Security에서 세션 관리를 구성하는 데 사용되는 메서드로, 중복 로그인 방지, 세션 무효화 전략, 최대 동시 세션 수 등과 같은 세션 관련 설정을 처리한다.sessionFixation().chagesSessionid()
: 사용자 인증이 성공하며 세션 ID를 변경하여 기존 세션을 무효화하고 새로운 세션을 생성한다. 세션 고정 공격을 방어할 수 있다.maximumSessions(1)
: 동시에 허용되는 세션 수를 설정한다. 한 사용자당 허용되는 최대 세션 수를 의미한다. maximumSessions(1)
을 설정하면 한 사용자가 한 번에 하나의 세션만 가질 수 있기 때문에, 이미 로그인한 상태에서 다른 장치나 브라우저로 로그인하려고 하면 기존 세션은 만료되고 새로운 세션으로 대체된다.expiredSessionStrategy(customSessionExpiredStrategy)
: 세션이 만료되었을 때 어떻게 처리할지 정의한다. customSessionExpiredStrategy
에서 정의한 대로 처리된다.maxSessionPreventsLogin(false)
: 동시에 로그인한 세션 수가 maximumSessions()
로 설정한 값에 도달했을 때, 새로운 로그인 시도를 허용할 지 여부를 설정한다.sessionRegistry(sessionRegistry())
: 동시에 로그인한 세션들을 추적하고 관리한다.SessionRegistry
, HttpSessionEventPublisher
컴포넌트 추가SessionRegistry
: 중복 로그인 방지를 위해 동시에 여러 세션이 열리지 않도록 한다.HttpSessionEventPublisher
: HttpSession
이벤트를 Spring 이벤트로 변환시킨다. 세션 생성 및 소멸 이벤트를 처리하는 데 도움이 된다.response.sendRedirect("/login");
@Component
public class CustomSessionExpiredStrategy implements SessionInformationExpiredStrategy {
@Override
public void onExpiredSessionDetected(SessionInformationExpiredEvent event) throws IOException, ServletException {
HttpServletRequest request = event.getRequest();
HttpServletResponse response = event.getResponse();
HttpSession session = request.getSession();
session.setAttribute("DUPLICATE_LOGIN", true);
response.sendRedirect("/login");
}
}
request.setAttribute
로 설정한 속성 값에 접근할 수 없다.request.getRequestDispatcher("/login").forward(request, response);
@Component
public class CustomSessionExpiredStrategy implements SessionInformationExpiredStrategy {
@Override
public void onExpiredSessionDetected(SessionInformationExpiredEvent event) throws IOException, ServletException {
HttpServletRequest request = event.getRequest();
HttpServletResponse response = event.getResponse();
request.setAttribute("DUPLICATE_LOGIN", true);
request.getRequestDispatcher("/login").forward(request, response);
}
}
DUPLICATE_LOGIN
값을 추가하여 구현했다. 서버 내에서 페이지만 이동하도록 구현하면 request 속성 값을 이동한 페이지에서도 사용할 수 있고, 세션처럼 따로 초기화를 하지 않아도 되기 때문에 더 효율적이라고 판단했다.public class CustomUserDetails implements UserDetails {
// ...
@Override
public boolean equals(Object obj) {
if (obj instanceof CustomUserDetails) {
return this.username.equals(((CustomUserDetails) obj).username);
}
return false;
}
@Override
public int hashCode() {
return this.username.hashCode();
}
}
equals
, hash
메서드 추가equals
, hash
등의 메서드에 대한 처리가 있어야 같은 유저임을 확인할 수 있다.<div class="duplicate-login-alert">
<c:if test="${DUPLICATE_LOGIN eq 'true'}">
<script> alert("다른 기기에서 로그인되어 현재 로그인이 종료되었습니다.") </script>
</c:if>
</div>
CustomSessionExpiredStrategy
에서 정의한 대로 request에 DUPLICATE_LOGIN = true
속성 값을 갖게 된다.login.jsp
에서 request 속성 값에 DUPLICATE_LOGIN = true
가 있다면, alert 창을 띄우고, login 페이지를 띄워준다.spring security 중복로그인 방지 처리 안될때.