我以为我已用此post解决了此问题。但是,我发现了一个导致问题的新用例,请原谅重复。我有一个使用Spring-Security 4.0.2的Spring-boot应用程序。我不允许并发登录,并且可以确认您无法在不同的浏览器中登录两次。我看到浏览器显然共享会话,因为我可以从同一浏览器的两个不同选项卡(Chrome,Safari,Firefox,IE11测试)登录。
我所看到的是,当我尝试第三次浏览器选项卡登录时,身份验证失败。这是预期和期望的行为,但不可接受的是用户帐户被锁定并且需要重新启动服务器。我检查数据库记录,该用户帐户仍然启用了所有内容。
我设置断点并逐步通过org.springframework.security.core.sessionSessionRegistryImpl
和
org.springframework.security.web.authenticationSimpleUrlAuthenticationFailureHandler
我无法看到用户被阻止的任何地方。我有我的调试级别:
logging.level.org.springframework:INFO
logging.level.org.springframework.web: INFO
logging.level.org.springframework.security:INFO
logging.level.org.springframework.security.web.authentication:INFO
我看不到任何异常或错误消息被抛出。这是我的主要配置:
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private CustomUserDetailsService customUserDetailsService;
@Autowired
private SessionRegistry sessionRegistry;
@Autowired
ServletContext servletContext;
@Autowired
private CustomLogoutHandler logoutHandler;
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth)
throws Exception {
auth
.userDetailsService(customUserDetailsService)
.passwordEncoder(passwordEncoder());
return;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.addFilterAfter(new CSRFTokenGeneratorFilter(), CsrfFilter.class)
.authorizeRequests()
.antMatchers("/", ).permitAll().anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/loginPage")
.permitAll()
.loginProcessingUrl("/login")
.defaultSuccessUrl("/?tab=success").failureUrl("/")
.and()
.logout().addLogoutHandler(logoutHandler).logoutRequestMatcher( new AntPathRequestMatcher("/logout"))
.deleteCookies("JSESSIONID")
.invalidateHttpSession(true).permitAll().and().csrf()
.and() // Instructs Spring Security to use the Servlet 3.1 changeSessionId method for protecting against session fixation attacks,
// also we set maximum session to 1
.sessionManagement().sessionAuthenticationStrategy(
concurrentSessionControlAuthenticationStrategy()).sessionFixation().changeSessionId().maximumSessions(1)
.maxSessionsPreventsLogin( true).expiredUrl("/login?expired" ).sessionRegistry(sessionRegistry )
.and()
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.invalidSessionUrl("/")
.and().authorizeRequests().anyRequest().authenticated();
// Here we protect site from:
// 1. X-Content-Type-Options
http.headers().contentTypeOptions();
// 2. Web Browser XSS Protection
http.headers().xssProtection();
http.headers().cacheControl();
http.headers().httpStrictTransportSecurity();
// 3. X-Frame-Options
http.headers().frameOptions();
// Inject servlet context and set http only (for increased security you should always customize http only to true)
servletContext.getSessionCookieConfig().setHttpOnly(true);
}
// CORS protection
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurerAdapter() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping( "/**" ).allowedOrigins( "*" )
.allowedHeaders( "Access-Control-Allow-Origin", "*" )
.allowedHeaders( "Access-Control-Allow-Headers", "x-requested-with" )
.allowedMethods( "GET", "POST", "PUT", "DELETE" )
.maxAge( 3600 );
}
};
}
@Bean
public PasswordEncoder passwordEncoder() {
// default strength = 10
return new BCryptPasswordEncoder();
}
@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;
}
}
我还有一个LogoutHandler来处理额外的会话(我想这就是Quchie所指的?):
@Component
public class CustomLogoutHandler implements LogoutHandler {
private static final Logger LOGGER = LoggerFactory.getLogger( CustomLogoutHandler.class );
@Autowired
private AuditService auditService;
@Autowired
private SessionRegistry sessionRegistry;
@Override
public void logout(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse,
Authentication authentication) {
if (authentication != null && authentication.getDetails() != null) {
try {
User user = (User)authentication.getPrincipal();
auditService.logoutUser(user.getOffice());
httpServletRequest.getSession().invalidate();
httpServletResponse.setStatus(HttpServletResponse.SC_OK);
//redirect to login
httpServletResponse.sendRedirect(Urls.ROOT);
List<SessionInformation> userSessions = sessionRegistry.getAllSessions(user, true);
for (SessionInformation session: userSessions) {
sessionRegistry.removeSessionInformation(session.getSessionId());
}
List<Object> principals = sessionRegistry.getAllPrincipals();
if (principals.contains(user)) {
throw new Exception("User not removed from session!");
}
LOGGER.debug( "user logged out" );
} catch (Exception e) {
e.printStackTrace();
e = null;
}
}
}
}
任何人都可以帮我找出如何防止用户帐户在失败的并发登录尝试中被锁定?或者至少让它有时间让它解锁有一定的时间量?
编辑: 如果只是在会话中存储,我无法理解为什么它会在很长一段时间(天)内锁定。数据库记录永远不会设置为错误状态,但在重新启动Tomcat之前,用户帐户将被锁定。