Spring Security通过并发登录尝试锁定用户

我对安全性感到陌生,并且遇到了一个问题,导致用户帐户被锁定,只有应用程序重新启动才能修复它。

我有一个春季启动(1.3.0.BUILD-SNAPSHOT)与弹簧安全(4.0.2.RELEASE)应用程序,我试图控制并发会话策略,所以用户只能有一个登录。 它能够正确检测到来自其他浏览器的后续登录尝试并阻止该尝试 但是,我注意到一些奇怪的行为,我似乎无法追查:

  • 用户可以在同一个浏览器中验证两个选项卡。 我无法用三个标签登录,但有两个作品。 注销一个似乎注销两者。 我看到cookie的值是一样的,所以我猜测他们正在分享一个会话:
  • 标签1 JSESSIONID: DA7C3EF29D55297183AF5A9BEBEF191F &941135CEBFA92C3912ADDC1DE41CFE9A

    标签2 JSESSIONID: DA7C3EF29D55297183AF5A9BEBEF191F &48C17A19B2560EAB8EC3FDF51B179AAE

    第二次登录尝试会显示以下日志消息,这似乎表明第二次登录尝试(我通过跳过Spring-Security源进行了验证:

    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
    
  • 当我使用两个选项卡登录然后注销时,用户帐户被锁定并需要重新启动服务器。 控制台中没有错误,并且数据库中的用户记录未更改。
  • 这是我的安全配置:

    @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;
      }
    }
    

    我也有以下方法来处理检查用户:

    @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;
        }
    }
    

    如何防止帐户被锁定,或者至少如何以编程方式解锁它们?

    编辑1/5/16我在WebSecurityConfig中添加了以下内容:

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

    并删除:

    servletContext.addListener(httpSessionEventPublisher())
    

    但是当我在同一个浏览器上登录两次时,我仍然会看到这种行为 - 注销锁定帐户直到我重新启动。


    事实证明,SessionRegistryImpl没有从会话中删除用户。 第一个标签注销从未实际上称为服务器,所以第二个调用确实会删除一个sessionid,并在主体中留下一个。

    我不得不做出几点改变:

    @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/31263.html

    上一篇: Spring Security locks user out with concurrent login attempts

    下一篇: git clone, "checking out files" phase skipped