2015-12-21 61 views
7

我對安全性感到陌生,並且遇到了導致用戶帳戶被鎖定的問題,只有應用程序重新啓動才能修復它。Spring Security以併發登錄嘗試鎖定用戶

我有一個春季開機(1.3.0.BUILD-SNAPSHOT)與彈簧安全(4.0.2.RELEASE)應用程序,我試圖控制併發會話策略,因此用戶只能有一個登錄。它能夠正確檢測到來自其他瀏覽器的後續登錄嘗試並阻止該嘗試但是,我注意到一些奇怪的行爲,我似乎無法追查:

  • 用戶可以在同一瀏覽器中驗證兩個選項卡。我無法用三個標籤登錄,但有兩個作品。註銷一個似乎註銷兩者。我看到了cookie值是相同的,所以我猜測他們正在共享會話:

標籤1 JSESSIONID:DA7C3EF29D55297183AF5A9BEBEF191F & 941135CEBFA92C3912ADDC1DE41CFE9A

標籤2 JSESSIONID:DA7C3EF29D55297183AF5A9BEBEF191F & 48C17A19B2560EAB8EC3FDF51B179AAE

第二次登錄嘗試提出這似乎表明第二次登錄嘗試(我踩着通春,安全源驗證以下日誌消息:

o.s.s.w.a.i.FilterSecurityInterceptor : Secure object: FilterInvocation: URL: /loginPage; Attributes: [permitAll] 
o.s.s.w.a.i.FilterSecurityInterceptor : Previously Authenticated: org.springframew[email protected]754041c8: Principal: User [[email protected], password=<somevalue> ]; Credentials: [PROTECTED]; Authenticated: true; Details: org.sprin[email protected]43458: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: 4708D404F64EE758662B2B308F36FFAC; Granted Authorities: Owner 
o.s.s.access.vote.AffirmativeBased  : Voter: org.sp[email protected]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; 
    } 
} 

如何防止帳戶從這樣鎖起來,或至少我如何以編程方式解鎖它們?

編輯16年1月5日 添加以下到我的WebSecurityConfig:

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

和刪除:

servletContext.addListener(httpSessionEventPublisher()) 

但我仍然看到的行爲,當我登錄上兩次同一個瀏覽器 - 註銷鎖定帳戶,直到我重新啓動。

+0

什麼Web瀏覽器是什麼呢?它發生在所有網頁瀏覽器上嗎? – JOW

+1

我注意到了OS X Safari/Firefox/Chrome,Win IE和Linux Firefox – sonoerin

+0

,因爲這是Spring特有的代碼問題,我認爲這會更好地問StackOverflow – schroeder

回答

3

事實證明,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; 
    } 
} 
相關問題