> Hello World !!!

     

@syaku

Spring security Concurrent Session Control, Remember Me : 스프링 시큐리티 중복로그인 , 로그인 기억 , 동시성세션제어

Spring security Concurrent Session Control, Remember Me : 스프링 시큐리티 중복로그인 , 로그인 기억 , 동시성세션제어

개발환경

Java 1.7 +
Java Servlet 3.1
Apache Tomcat 7.x
Spring Freamwork 4.2.4.RELEASE
Spring security 4.1.0.RELEASE

링크

Github : https://github.com/syakuis/spring-security-dlc

스프링 시큐리티를 이용하여 동일한 사용자가 로그인하지 못하게 막거나, 유일하게 최근 로그인 사용자 한명만 로그인을 유효하게 만들 수 있다. 나는 이런 일반적인 기능을 설명하려는 것이 아니다. 내가 구현하려는 것은 로그인을 할때 기존 로그인 사용자가 있으면 먼저 메세지로 알려주어 로그인 의사를 묻는 것(앞으로 줄여서 중복로그인 이라고 말하겠다)과 로그인 기억(Rememberme)을 했을때도 기존 설정에 따라 작동되게 하는 것이다.

중복로그인을 비동기 방식(ajax)으로 구현한다면 필터를 이용하여 간단하게 구현할 수 있다. 하지만 (1)동기와 비동기 방식 모두를 지원하는 게 목적이라 필터로 구현이 자연스럽지 못했다. 결국 두번의 요청이 필요했고, 첫번째 요청이 기존의 사용자 세션이 존재하는 지를 파악하여 메세지를 전달하고 두번째 요청은 첫번째 메세지에서 참을 얻을 경우 로그인 처리를 진행하게 한다. 첫번째는 무조건 비동기방식으로 구현하고 두번째는 비동기든 동기든 상관없이 구현하여도 된다.

결국 간단하게 자연스러운 흐름을 구현할 수 있게 되었다.

이제 남은 건 로그인 기억. 중복로그인을 설정하고 로그인 기억으로 로그인하게 되면 기존에 로그인 했던 세션을 찾지 못하고 새로운 세션이 만들어지는 문제가 생긴다. sessionRegistry에서도 조회할 수 없는 세션으로 즉 유령사용자가 되는 것이다. 결국 스프링 시큐리티 메뉴얼에서 해답을 얻었다. 영어를 잘 몰라서 대충 느낌으로 번역을 보니 rememberme 설정에서 동시 세션 제어가 되지 않기 때문에 직접 구현해야한다라는 부분이 있었다. 이것때문에 얼마나 시간을 허비했는 지... 

[참고] http://docs.spring.io/spring-security/site/docs/4.1.3.RELEASE/reference/htmlsingle/#d5e3442
http://docs.spring.io/spring-security/site/docs/4.1.3.RELEASE/reference/htmlsingle/#ftn.d5e3442

나는 중복로그인과 로그인 기억을 구현하기 위해 (2)기본 설정(spring security)을 최대한 활요하는 것이 목적이라 직접 자바를 이용하여 커스텀(시큐리티 필터나 스프링 빈)하게 개발하지 않으려고 했었다. 왜냐하면 스프링 시큐리티의 기본 설정들에 대한 다시 한번 자세히 분석해보고 싶어서였다. 그래서 기본 설정에서 해답을 찾다보니 시간을 낭비하게 된 것 같다. 결국 rememberme는 동시 세션 제어를 동작할 수 있게 커스텀 작업이 필요했던 것이니...

그렇게 만들어지게 된 결과는 아래의 데모 영상을 참고하면 된다.

이제 어떻게 구현하였는 지 소스를 보고 하나하나 알아보자. 자세한 소스는 Github에 등록된 것을 참고한다.

< / > src/main/resources/config/security-context.xml

소스내에 주석으로 설명을 해두었으니 참고하길 바란다.

<util:map id="config" ... >

<b:entry key="duplicationLoginDisable" value="true" />
<b:entry key="maximumSessions" value="-1"/>
<b:entry key="exceptionIfMaximumExceeded" value="false"/>

</util:map>

매번 properties를 이용하여 설정 정보로 사용했다. 하지만 간단한 예제나 프로그램을 만들때는 위처럼 util을 사용하는 것이 더 좋을 것 같아 시도해보았다. config는 자주 변경될 것 같은 설정들을 모아 쉽게 수정할 수 있게 만들어두었다. duplicationLoginDisable 옵션이 있는 데 이 값이 false 인 경우 중복로그인을 검사하게 되며 maximumSessions = 1, exceptionIfMaximumExceeded = false 강제로 설정되게 된다.

위 두개 옵션은 동시 세션 제어를 위한 설정이다. maximumSessions은 해당 수 만큼 세션을 생성할 수 있고 해당 수만큼 세션이 만들어졌다면 가장 먼저 생성된 세션이 파기되고 새로운 새션이 만들어 진다. 하지만 설정 수보다 더 세션을 만들지 않으려면 exceptionIfMaximumExceeded true로 설정하면 더 이상세션이 만들어지지 않는 다.

동시 세션 제어는 브라이저가 닫기거나 알수 없는 이유로 세션을 잃어버리게 된다면(컴퓨터가 꺼지거나, 브라어저가 강제종료 되거나 등등) 제어할 수 없게 된다. 만료될때까지 세션이 존재하게 될 것이다. 만약 더이상 세션을 만들 수 없게 된다면 더이상 로그인할 수 없게 된다.

<http auto-config="false" use-expressions="true" entry-point-ref="authenticationEntryPoint">
    
    <custom-filter ref="concurrentSessionFilter" position="CONCURRENT_SESSION_FILTER" />
            <custom-filter ref="usernamePasswordAuthenticationFilter" position="FORM_LOGIN_FILTER" />
            <custom-filter ref="logoutFilter" position="LOGOUT_FILTER" />
            <custom-filter ref="rememberMeFilter" position="REMEMBER_ME_FILTER" />

</http>

auto-config="false" 설정하는 것을 스프링 시큐리티에서도 권장한다. true인 경우 스프링 시큐리티가 설정해둔 필터로 구성되어 작동되게 된다. 그래서 나도 false를 하고 필요한 필터들을 직접 구현하였다.

  • concurrentSessionFilter : 동시 세션 제어 필터이다. 세션을 정리하는 역활을 한다.
  • usernamePasswordAuthenticationFilter : 설정에 해당하는 필터이고 로그인 처리를 담당한다.
  • logoutFilter : 설정에 해당하는 필터이고 로그아웃 처리를 담당한다.

프로젝트를 하다보면 로그인 처리를 담당하는 필터에서 해야할 작업들이 많이 생긴다. 하지만 로그아웃 필터처럼 로그인 처리에는 헨들러를 여러개 주입할 수 없다. 그래서 나는 로그인 필터를 로그이아웃 필터처럼 여러개의 헨들러를 주입할 수 있게 개선하였으며 로그인 처리 전 처리 후 로그인 완료에 관한 트리거를 만들어 전처리를 가능하게도 하였다. 이번 포스트에는 설명이 없지만 구현하는 것은 어렵지 않을 것이다.

<b:bean id="concurrentSessionFilter" class="org.springframework.security.web.session.ConcurrentSessionFilter">
        <!-- 세션이 만료되었을 경우 이동할 페이지를 설정한다. -->
        <b:constructor-arg name="expiredUrl" value="#{config.sessionExpiredUrl}" />
        <b:constructor-arg name="sessionRegistry" ref="sessionRegistry" />
        <b:property name="logoutHandlers">
            <b:array>
                <b:ref bean="rememberMeServices" />
                <b:ref bean="securityContextLogoutHandler" />
                <b:ref bean="cookieClearingLogoutHandler"/>
            </b:array>
        </b:property>
    </b:bean>

concurrentSessionFilter 에는 꼭 로그아웃 헨들러를 주입해줘야 한다. 로그아웃 헨들러에는 rememberme 서비스가 있는 데 이걸 넣지 않을 경우 세션을 강제로 제거하더라도 좀비처럼 세션이 다시 살아날 것이다. (로그인을 로그인 기억으로 한 경우 해당함)

동시 세션 제어를 위해 아래의 설정이 세트로 이루어지므로 꼭 아래와 같이 설정해야 한다. 완전한 소스는 github를 참고한다.

<http>
<custom-filter ref="concurrentSessionFilter" position="CONCURRENT_SESSION_FILTER" />

<custom-filter ref="usernamePasswordAuthenticationFilter" position="FORM_LOGIN_FILTER" />

<session-management session-authentication-strategy-ref="sas" />
</http>

<b:bean id="sessionRegistry" class="org.springframework.security.core.session.SessionRegistryImpl" />

<b:bean id="usernamePasswordAuthenticationFilter" class="org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter">
    ... skip ...
</b:bean>

<b:bean id="concurrentSessionFilter" class="org.springframework.security.web.session.ConcurrentSessionFilter">
    ... skip ...
</b:bean>


<b:bean id="sas" class="org.springframework.security.web.authentication.session.CompositeSessionAuthenticationStrategy">
    ... skip ...
</b:bean>

로그인 기억(rememberme)를 설정하기 위한 세트는 아래와 같다.

<http>
    <custom-filter ref="rememberMeFilter" position="REMEMBER_ME_FILTER" />
</http>

<b:bean id="rememberMeFilter" class="org.syaku.spring.security.filter.RememberMeConcurrentSessionFilter">
    ... skip ...
</b:bean>

<b:bean id="rememberMeServices" class="org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices">
    ... skip ...
</b:bean>

<b:bean id="rememberMeAuthenticationProvider" class="org.springframework.security.authentication.RememberMeAuthenticationProvider">
    ... skip ...
</b:bean>

rememberMeFilter 에서 동시 세션 제어를 주입하여 호출하게 하여 세션을 동기화 시킨다. 설명은 여기까지이다. 직접 소스를 내려받아 구현해보면 좀 더 이해가 빠를 것 이다.

Keyword

session-management rememberMeFilter rememberMeServices rememberMeAuthenticationProvider sessionRegistry usernamePasswordAuthenticationFilter sessionAuthenticationStrategy concurrentSessionFilter CompositeSessionAuthenticationStrategy ConcurrentSessionControlAuthenticationStrategy SessionFixationProtectionStrategy RegisterSessionAuthenticationStrategy