Spring Security locks user out with concurrent login attempts

I am new to security and have run into a problem that is causing the user account to be locked in such a fashion that only a application restart fixes it.

I have a spring boot (1.3.0.BUILD-SNAPSHOT) with spring security (4.0.2.RELEASE) app that I am trying to control the concurrent session strategy so a user can only have a single login. It correctly detects subsequent login attempts from another browser and prevents that. However, I have noticed some odd behavior that i can't seem to track down:

  • A User can have two tabs authenticated in the same browser. I can't login with three tabs, but two works. Logging out of one seems to logout of both. I see the cookie values are the same, so I am guessing they are sharing a session:
  • tab 1 JSESSIONID: DA7C3EF29D55297183AF5A9BEBEF191F & 941135CEBFA92C3912ADDC1DE41CFE9A

    tab 2 JSESSIONID: DA7C3EF29D55297183AF5A9BEBEF191F & 48C17A19B2560EAB8EC3FDF51B179AAE

    A second login attempt presents the following log messages which seems to indicate a second login attempt (which I verified by stepping thru the Spring-Security source:

    o.s.s.w.a.i.FilterSecurityInterceptor    : Secure object: FilterInvocation: URL: /loginPage; Attributes: [permitAll]
     o.s.s.w.a.i.FilterSecurityInterceptor    : Previously Authenticated: org.springframework.security.authentication.UsernamePasswordAuthenticationToken@754041c8: Principal: User [username=xxx@xxx.xx, password=<somevalue> ]; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@43458: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: 4708D404F64EE758662B2B308F36FFAC; Granted Authorities: Owner
     o.s.s.access.vote.AffirmativeBased       : Voter: org.springframework.security.web.access.expression.WebExpressionVoter@17527bbe, returned: 1
     o.s.s.w.a.i.FilterSecurityInterceptor    : Authorization successful
     o.s.s.w.a.i.FilterSecurityInterceptor    : RunAsManager did not change Authentication object
     o.s.security.web.FilterChainProxy        : /loginPage reached end of additional filter chain; proceeding with original chain
     org.apache.velocity                      : ResourceManager : unable to find resource 'loginPage.vm' in any resource loader.
     o.s.s.w.a.ExceptionTranslationFilter     : Chain processed normally
     s.s.w.c.SecurityContextPersistenceFilter : SecurityContextHolder now cleared, as request processing completed
    
  • When I log in with two tabs and then logout, the user account becomes locked up and requires a server restart. There are no errors in the console and the user records in the db are unchanged.
  • Here is my security config:

    @Configuration
    @EnableWebSecurity
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    
          @Autowired
          private CustomUserDetailsService customUserDetailsService;
    
          @Autowired
          private SessionRegistry sessionRegistry;
    
          @Autowired
          ServletContext servletContext;
    
          @Autowired
          private CustomLogoutHandler logoutHandler;
    
          @Autowired
          private MessageSource messageSource;
    
    
    /**
     * Sets security configurations for the authentication manager
     */
    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth)
                    throws Exception {
        auth
                        .userDetailsService(customUserDetailsService)
                        .passwordEncoder(passwordEncoder());
        return;
    }  
      protected void configure(HttpSecurity http) throws Exception {
            http
               .formLogin()
               .loginPage("/loginPage")
               .permitAll()
               .loginProcessingUrl("/login")
               .defaultSuccessUrl("/?tab=success")
               .and()
                  .logout().addLogoutHandler(logoutHandler).logoutRequestMatcher( new AntPathRequestMatcher("/logout"))
                  .deleteCookies("JSESSIONID")
                   .invalidateHttpSession(true).permitAll().and().csrf()
               .and()  
                  .sessionManagement().sessionAuthenticationStrategy(             concurrentSessionControlAuthenticationStrategy).sessionFixation().changeSessionId().maximumSessions(1)
                  .maxSessionsPreventsLogin( true).expiredUrl("/login?expired" ).sessionRegistry(sessionRegistry )
              .and()
               .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
               .invalidSessionUrl("/")
               .and().authorizeRequests().anyRequest().authenticated();
            http.headers().contentTypeOptions();
            http.headers().xssProtection();
            http.headers().cacheControl();
            http.headers().httpStrictTransportSecurity();
            http.headers().frameOptions();
            servletContext.getSessionCookieConfig().setHttpOnly(true);
        }
    
      @Bean
      public ConcurrentSessionControlAuthenticationStrategy concurrentSessionControlAuthenticationStrategy() {
    
          ConcurrentSessionControlAuthenticationStrategy strategy = new ConcurrentSessionControlAuthenticationStrategy(sessionRegistry());
          strategy.setExceptionIfMaximumExceeded(true);
          strategy.setMessageSource(messageSource);
    
          return strategy;
      }
    
      // Work around https://jira.spring.io/browse/SEC-2855
      @Bean
      public SessionRegistry sessionRegistry() {
          SessionRegistry sessionRegistry = new SessionRegistryImpl();
          return sessionRegistry;
      }
    }
    

    I also have the following methods to handle checking user:

    @Entity
    @Table(name = "USERS")
    @JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class,
                      property = "username")
    public class User implements UserDetails {
    ...
     @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result + ((username == null) ? 0 : username.hashCode());
            return result;
        }
    
        @Override
        public boolean equals(Object obj) {
            if (this == obj)
                return true;
            if (obj == null)
                return false;
            if (getClass() != obj.getClass())
                return false;
            User other = (User) obj;
            if (username == null) {
                if (other.username != null)
                    return false;
            } else if (!username.equals(other.username))
                return false;
            return true;
        }
    }
    

    How do I prevent the account from locking up like that, or at least how do I unlock them programmatically?

    Edit 1/5/16 I added the following to my WebSecurityConfig:

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

    and removed:

    servletContext.addListener(httpSessionEventPublisher())
    

    But I still see the behavior when I log in twice on the same browser - logging out locks the account until I restart.


    It turns out that the SessionRegistryImpl was not removing the user from the session. The first tab logout never actually called the server, so the second call does remove the one sessionid, leaving one in the principals.

    I had to make several changes:

    @Component
    public class CustomLogoutHandler implements LogoutHandler {
        @Autowired
        private SessionRegistry sessionRegistry;
    
        @Override
        public void logout(HttpServletRequest httpServletRequest, httpServletResponse httpServletResponse, Authentication authentication) {
    ...
    httpServletRequest.getSession().invalidate();
    httpServletResponse.setStatus(HttpServletResponse.SC_OK);
    //redirect to login
    httpServletResponse.sendRedirect("/");
        List<SessionInformation> userSessions = sessionRegistry.getAllSessions(user, true);
    
        for (SessionInformation session: userSessions) {
            sessionRegistry.removeSessionInformation(session.getSessionId());
        }
    }
    
    @Configuration
    @EnableWebSecurity
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    
    @Bean
        public SessionRegistry sessionRegistry() {
            if (sessionRegistry == null) {
                sessionRegistry = new SessionRegistryImpl();
            }
            return sessionRegistry;
        }
    
        @Bean
        public static ServletListenerRegistrationBean httpSessionEventPublisher() {
            return new ServletListenerRegistrationBean(new HttpSessionEventPublisher());
        }
    
        @Bean
        public ConcurrentSessionControlAuthenticationStrategy concurrentSessionControlAuthenticationStrategy() {
    
            ConcurrentSessionControlAuthenticationStrategy strategy = new ConcurrentSessionControlAuthenticationStrategy(sessionRegistry());
            strategy.setExceptionIfMaximumExceeded(true);
            strategy.setMessageSource(messageSource);
    
            return strategy;
        }
    }
    
    链接地址: http://www.djcxy.com/p/31264.html

    上一篇: SaltStack:来自SLS文件的数据的属性(计算值)?

    下一篇: Spring Security通过并发登录尝试锁定用户