Spring security 4 disallow concurrent login

I am trying to create a form based user login with custom User and UserDetails in spring-security4. I want to prevent simultaneous logins from the same user (so that a user can only log into 1 device).

However, if I am using two different browsers, I can log into both browsers with the same user account (which is not desirable).

I found some similar problems and solutions, but unfortunately the solutions didn't seem to work in my case.

Please note that I am using spring-boot so I have no web.xml

I created a config file that looks like this:

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity( securedEnabled = true )
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    public SecurityConfig(UserDetailsService userServ) {
        userService = userServ;
    }

    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

    @Bean
    public ServletListenerRegistrationBean<HttpSessionEventPublisher>  httpSessionEventPublisher() {
        return new ServletListenerRegistrationBean<HttpSessionEventPublisher>(new HttpSessionEventPublisher());
    }

    @Autowired
    public void configureAuth(AuthenticationManagerBuilder auth) throws Exception{
        auth
            .userDetailsService(userService)
            .passwordEncoder(passwordEncoder());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()

            // Allow anonymous access to the static resources
            .antMatchers("/css/**").permitAll()
            .antMatchers("/font-awesome/**").permitAll()
            .antMatchers("/fonts/**").permitAll()
            .antMatchers("/img/**").permitAll()
            .antMatchers("/js/**").permitAll()

            // Need to be account admin in your business to control other accounts
            .antMatchers("/app/mybusiness/users").hasRole("ACCOUNT_ADMIN")

            .anyRequest().authenticated()
            .and()
            .formLogin()
            .loginPage("/auth/login")
            .usernameParameter("email")
            .permitAll()
            .and()
            .logout()
            .logoutSuccessUrl("/auth/login?logout")
            .and()
            .sessionManagement()
            .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
            .sessionFixation()
            .migrateSession()
            .maximumSessions(1)
            .maxSessionsPreventsLogin(true);

    }

    private UserDetailsService userService;
}

      

Thank you in advance

EDIT:

As some of you pointed out, I forgot to add .maxSessionsPreventsLogin (true); in my code. I added this to my code, but unfortunately nothing has changed, I can still log into the same account using two different browsers (Safari and Firefox).

I was prompted to add spring security logs with DEBUG enabled.

First, I connected to Safari and registered with the app. After logging into the security log, spring looked like this:

********************************************************************
**********        Security debugging is enabled.       *************
**********    This may include sensitive information.  *************
**********      Do not use in a production system!     *************
********************************************************************
2017-06-20 20:19:57.061  INFO 810 --- [  restartedMain]
o.s.b.d.a.OptionalLiveReloadServer       : LiveReload server is running on port 35729
2017-06-20 20:19:57.133  INFO 810 --- [  restartedMain] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2017-06-20 20:19:57.199  INFO 810 --- [  restartedMain] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080 (http)
2017-06-20 20:19:57.205  INFO 810 --- [  restartedMain] com.processbyte.PmsApplication           : Started PmsApplication in 19.414 seconds (JVM running for 19.979)
2017-06-20 20:20:01.585  INFO 810 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring FrameworkServlet 'dispatcherServlet'
2017-06-20 20:20:01.585  INFO 810 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : FrameworkServlet 'dispatcherServlet': initialization started
2017-06-20 20:20:01.600  INFO 810 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : FrameworkServlet 'dispatcherServlet': initialization completed in 15 ms
2017-06-20 20:20:01.609  INFO 810 --- [nio-8080-exec-1] Spring Security Debugger                 :
************************************************************
Request received for GET '/app/myaccount':
org.apache.catalina.connector.RequestFacade@e90e20
servletPath:/app/myaccount
pathInfo:null

headers: 
host: localhost:8080
accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
upgrade-insecure-requests: 1
cookie: JSESSIONID=BC2B8F18111AB83F76BE3015A75B0F22
user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5)     AppleWebKit/603.2.4 (KHTML, like Gecko) Version/10.1.1 Safari/603.2.4
accept-language: en-us
accept-encoding: gzip, deflate
connection: keep-alive


Security filter chain: [
    WebAsyncManagerIntegrationFilter
    SecurityContextPersistenceFilter
    HeaderWriterFilter
    CsrfFilter
    LogoutFilter
    UsernamePasswordAuthenticationFilter
    ConcurrentSessionFilter
    RequestCacheAwareFilter
    SecurityContextHolderAwareRequestFilter
    AnonymousAuthenticationFilter
    SessionManagementFilter
    ExceptionTranslationFilter
    FilterSecurityInterceptor
]


************************************************************


2017-06-20 20:20:01.639  INFO 810 --- [nio-8080-exec-2] Spring Security    Debugger                 : 

************************************************************

Request received for GET '/auth/login':

org.apache.catalina.connector.RequestFacade@e90e20

servletPath:/auth/login
pathInfo:null
headers: 
host: localhost:8080
accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
upgrade-insecure-requests: 1
cookie: JSESSIONID=BC2B8F18111AB83F76BE3015A75B0F22
user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5)     AppleWebKit/603.2.4 (KHTML, like Gecko) Version/10.1.1 Safari/603.2.4
accept-language: en-us
accept-encoding: gzip, deflate
connection: keep-alive


Security filter chain: [
    WebAsyncManagerIntegrationFilter
    SecurityContextPersistenceFilter
    HeaderWriterFilter
    CsrfFilter
    LogoutFilter
    UsernamePasswordAuthenticationFilter
    ConcurrentSessionFilter
    RequestCacheAwareFilter
    SecurityContextHolderAwareRequestFilter
    AnonymousAuthenticationFilter
    SessionManagementFilter
    ExceptionTranslationFilter
    FilterSecurityInterceptor
]


************************************************************


2017-06-20 20:21:18.862  INFO 810 --- [nio-8080-exec-5] Spring Security Debugger                 : 

************************************************************

Request received for POST '/auth/login':

org.apache.catalina.connector.RequestFacade@740e4169

servletPath:/auth/login
pathInfo:null
headers: 
host: localhost:8080
accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
accept-encoding: gzip, deflate
accept-language: en-us
content-type: application/x-www-form-urlencoded
origin: http://localhost:8080
user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/603.2.4 (KHTML, like Gecko) Version/10.1.1 Safari/603.2.4
connection: keep-alive
upgrade-insecure-requests: 1
referer: http://localhost:8080/auth/login
content-length: 90
cookie: JSESSIONID=BC2B8F18111AB83F76BE3015A75B0F22


Security filter chain: [
    WebAsyncManagerIntegrationFilter
    SecurityContextPersistenceFilter
    HeaderWriterFilter
    CsrfFilter
    LogoutFilter
    UsernamePasswordAuthenticationFilter
    ConcurrentSessionFilter
    RequestCacheAwareFilter
    SecurityContextHolderAwareRequestFilter
    AnonymousAuthenticationFilter
    SessionManagementFilter
    ExceptionTranslationFilter
    FilterSecurityInterceptor
]


************************************************************


2017-06-20 20:21:18.891  INFO 810 --- [nio-8080-exec-5]  org.mongodb.driver.connection            : Opened connection  [connectionId{localValue:2, serverValue:7}] to localhost:27017
2017-06-20 20:21:19.103  INFO 810 --- [nio-8080-exec-3] Spring Security  Debugger                 : 

************************************************************
Request received for GET '/app/myaccount':

org.apache.catalina.connector.RequestFacade@740e4169

servletPath:/app/myaccount
pathInfo:null
headers: 
host: localhost:8080
origin: http://localhost:8080
cookie: JSESSIONID=846CA88C32032533628A31839F083F6D
connection: keep-alive
upgrade-insecure-requests: 1
accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
accept-language: en-us
referer: http://localhost:8080/auth/login
accept-encoding: gzip, deflate
user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5)   AppleWebKit/603.2.4 (KHTML, like Gecko) Version/10.1.1 Safari/603.2.4


Security filter chain: [
    WebAsyncManagerIntegrationFilter
    SecurityContextPersistenceFilter
    HeaderWriterFilter
    CsrfFilter
    LogoutFilter
    UsernamePasswordAuthenticationFilter
    ConcurrentSessionFilter
    RequestCacheAwareFilter
    SecurityContextHolderAwareRequestFilter
    AnonymousAuthenticationFilter
    SessionManagementFilter
    ExceptionTranslationFilter
    FilterSecurityInterceptor
]

      

Later I connected from Firefox to the same url and logged in with the same user account, spring security log was added like this:

2017-06-20 20:23:17.381  INFO 810 --- [nio-8080-exec-4] Spring Security Debugger                 :
************************************************************

Request received for GET '/app/myaccount':

org.apache.catalina.connector.RequestFacade@60b5387b

servletPath:/app/myaccount
pathInfo:null
headers: 
host: localhost:8080
user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:53.0)     Gecko/20100101 Firefox/53.0
accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
accept-language: en-US,en;q=0.5
accept-encoding: gzip, deflate
connection: keep-alive
upgrade-insecure-requests: 1


Security filter chain: [
    WebAsyncManagerIntegrationFilter
    SecurityContextPersistenceFilter
    HeaderWriterFilter
    CsrfFilter
    LogoutFilter
    UsernamePasswordAuthenticationFilter
    ConcurrentSessionFilter
    RequestCacheAwareFilter
    SecurityContextHolderAwareRequestFilter
    AnonymousAuthenticationFilter
    SessionManagementFilter
    ExceptionTranslationFilter
    FilterSecurityInterceptor
]


************************************************************

2017-06-20 20:23:17.388  INFO 810 --- [nio-8080-exec-4] Spring Security Debugger                 : 

************************************************************

New HTTP session created: 62F06B8BD67C21B90E7FE199C740767B

Call stack: 

at org.springframework.security.web.debug.Logger.info(Logger.java:44)
at org.springframework.security.web.debug.DebugRequestWrapper.getSession(DebugFilter.java:166)
at javax.servlet.http.HttpServletRequestWrapper.getSession(HttpServletRequestWrapper.java:240)
at javax.servlet.http.HttpServletRequestWrapper.getSession(HttpServletRequestWrapper.java:240)
at javax.servlet.http.HttpServletRequestWrapper.getSession(HttpServletRequestWrapper.java:240)
at org.springframework.security.web.savedrequest.HttpSessionRequestCache.saveRequest(HttpSessionRequestCache.java:59)
at org.springframework.security.web.access.ExceptionTranslationFilter.sendStartAuthentication(ExceptionTranslationFilter.java:201)
at org.springframework.security.web.access.ExceptionTranslationFilter.handleSpringSecurityException(ExceptionTranslationFilter.java:177)
at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:133)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:137)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:111)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:170)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:63)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
at org.springframework.security.web.session.ConcurrentSessionFilter.doFilter(ConcurrentSessionFilter.java:155)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:200)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:116)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
at org.springframework.security.web.csrf.CsrfFilter.doFilterInternal(CsrfFilter.java:100)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:64)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:105)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:56)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:214)
at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:177)
at org.springframework.security.web.debug.DebugFilter.invokeWithWrappedRequest(DebugFilter.java:90)
at org.springframework.security.web.debug.DebugFilter.doFilter(DebugFilter.java:77)
at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:346)
at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:262)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.HttpPutFormContentFilter.doFilterInternal(HttpPutFormContentFilter.java:105)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:81)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:197)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:198)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:478)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:140)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:80)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:342)
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:799)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:861)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1455)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:748)


************************************************************


2017-06-20 20:23:17.408  INFO 810 --- [nio-8080-exec-5] Spring Security Debugger                 : 

************************************************************

Request received for GET '/auth/login':

org.apache.catalina.connector.RequestFacade@60b5387b

servletPath:/auth/login
pathInfo:null
headers: 
host: localhost:8080
user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:53.0) Gecko/20100101 Firefox/53.0
accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
accept-language: en-US,en;q=0.5
accept-encoding: gzip, deflate
cookie: JSESSIONID=62F06B8BD67C21B90E7FE199C740767B
connection: keep-alive
upgrade-insecure-requests: 1


Security filter chain: [
    WebAsyncManagerIntegrationFilter
    SecurityContextPersistenceFilter
    HeaderWriterFilter
    CsrfFilter
    LogoutFilter
    UsernamePasswordAuthenticationFilter
    ConcurrentSessionFilter
    RequestCacheAwareFilter
    SecurityContextHolderAwareRequestFilter
    AnonymousAuthenticationFilter
    SessionManagementFilter
    ExceptionTranslationFilter
    FilterSecurityInterceptor
]


************************************************************


2017-06-20 20:23:23.349  INFO 810 --- [nio-8080-exec-5] Spring Security Debugger                 : 

************************************************************

Request received for POST '/auth/login':

org.apache.catalina.connector.RequestFacade@196c2c

servletPath:/auth/login
pathInfo:null
headers: 
host: localhost:8080
user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:53.0)    Gecko/20100101 Firefox/53.0
accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
accept-language: en-US,en;q=0.5
accept-encoding: gzip, deflate
referer: http://localhost:8080/auth/login
content-type: application/x-www-form-urlencoded
content-length: 90
cookie: JSESSIONID=62F06B8BD67C21B90E7FE199C740767B
connection: keep-alive
upgrade-insecure-requests: 1


Security filter chain: [
    WebAsyncManagerIntegrationFilter
    SecurityContextPersistenceFilter
    HeaderWriterFilter
    CsrfFilter
    LogoutFilter
    UsernamePasswordAuthenticationFilter
    ConcurrentSessionFilter
    RequestCacheAwareFilter
    SecurityContextHolderAwareRequestFilter
    AnonymousAuthenticationFilter
    SessionManagementFilter
    ExceptionTranslationFilter
    FilterSecurityInterceptor
]


************************************************************


2017-06-20 20:23:23.504  INFO 810 --- [nio-8080-exec-7] Spring Security Debugger                 : 

************************************************************

Request received for GET '/app/myaccount':

org.apache.catalina.connector.RequestFacade@196c2c

servletPath:/app/myaccount
pathInfo:null
headers: 
host: localhost:8080
user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:53.0)    Gecko/20100101 Firefox/53.0
accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
accept-language: en-US,en;q=0.5
accept-encoding: gzip, deflate
referer: http://localhost:8080/auth/login
cookie: JSESSIONID=8D8FE6BC6BEDCAB6529E30E2775817E6
connection: keep-alive
upgrade-insecure-requests: 1


Security filter chain: [
    WebAsyncManagerIntegrationFilter
    SecurityContextPersistenceFilter
    HeaderWriterFilter
    CsrfFilter
    LogoutFilter
    UsernamePasswordAuthenticationFilter
    ConcurrentSessionFilter
    RequestCacheAwareFilter
    SecurityContextHolderAwareRequestFilter
    AnonymousAuthenticationFilter
    SessionManagementFilter
    ExceptionTranslationFilter
    FilterSecurityInterceptor
]

      

+3


source to share


2 answers


Thanks everyone for your answers, as it turned out I made a few mistakes. I edited the code in my question to match the feedback I received. However, one more thing needs to be done to solve this problem.

Since I am using a custom UserDetails implementation, I had to override equals and hashCode in the UserDetailsImpl class.



@Override
public boolean equals(Object otherUser) {
    if(otherUser == null) return false;
    else if (!(otherUser instanceof UserDetails)) return false;
    else return (otherUser.hashCode() == hashCode());
}

@Override
public int hashCode() {
    return user.getEmail().hashCode() ;
}

      

click here for source

+2


source


Add the following to the last line:

.sessionManagement().maximumSessions(1).maxSessionsPreventsLogin(true);

      



YMMV, I haven't tested this, just looked at the documentation

0


source







All Articles