我是安全方面的新手,遇到了一个导致用户帐户被锁定的问题,只有重新启动应用程序才能修复它。
我有一个带有 spring security (4.0.2.RELEASE) 应用程序的 spring boot (1.3.0.BUILD-SNAPSHOT),我试图控制并发 session 策略,以便用户只能进行一次登录。它正确地检测到来自另一个浏览器的后续登录尝试并阻止了这种情况。但是,我注意到一些我似乎无法追踪的奇怪行为:
- 一个用户可以在同一个浏览器中验证两个选项卡。我无法使用三个选项卡登录,但有两个可以。注销一个似乎注销两个。我看到 cookie 值相同,所以我猜他们正在共享一个 session :
tab 1 JSESSIONID: DA7C3EF29D55297183AF5A9BEBEF191F & 941135CEBFA92C3912ADDC1DE41CFE9A
tab 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 并未从 session 中删除用户。第一个选项卡注销实际上从未调用过服务器,因此第二个调用确实删除了一个 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;
}
}
关于java - Spring Security 通过并发登录尝试锁定用户,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34401172/