Spring Security maxSession不起作用

时间:2016-06-18 01:55:57

标签: spring spring-security spring-session

我希望在用户超过maxSession计数时阻止登录。例如,每个用户都可以登录一次。然后,如果登录用户尝试另一个登录系统应该禁用他的登录。

.sessionManagement()
.maximumSessions(1).expiredUrl("/login?expire").maxSessionsPreventsLogin(true)
.sessionRegistry(sessionRegistry());


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

3 个答案:

答案 0 :(得分:3)

注意:这是在 Spring MVC 4.3.9.RELEASE 上测试的,我还没有使用过Spring Boot。

我找到了一个解决方案,让我分享它是如何与我合作的。

1)我使用SessionManagement配置了HttpSecurity,如下所示:

@Override
protected void configure(HttpSecurity http) throws Exception {
  http
    .authorizeRequests()
      .antMatchers("/resources/**").permitAll()
      .antMatchers("/login**").permitAll()        // 1
      .antMatchers(...)
      .anyRequest().authenticated()
      .and()
    .formLogin()
      .loginPage("/login")
      .permitAll()
      .and()
    .logout()
      .deleteCookies("JSESSIONID")
      .permitAll()
      .and()
    .sessionManagement()                          // 2
      .maximumSessions(1)                         // 3
        .maxSessionsPreventsLogin(false)          // 4
        .expiredUrl("/login?expired")             // 5
        .sessionRegistry(getSessionRegistry())    // 6
    ;           
}

在文档Spring Doc > HttpSecurity > sessionManagement()

的帮助下
  

示例配置

     

以下配置演示了如何仅强制执行   一次验证用户的单个实例。如果是用户   使用用户名" user"进行身份验证没有退出和   尝试使用" user"进行身份验证是第一次会议   强行终止并发送到" / login?过期" URL。

@Configuration  
@EnableWebSecurity  
public class SessionManagementSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
            http.authorizeRequests().anyRequest().hasRole("USER").and().formLogin()
                            .permitAll().and().sessionManagement().maximumSessions(1)
                            .expiredUrl("/login?expired");
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            auth.inMemoryAuthentication().withUser("user").password("password").roles("USER");
    }  }   
     

使用SessionManagementConfigurer.maximumSessions(int)时,不要忘记   为应用程序配置HttpSessionEventPublisher以确保这一点   过期的会话被清理干净。在web.xml中,可以配置它   使用以下内容:

<listener>
      <listener-class>org.springframework.security.web.session.HttpSessionEventPublisher</listener-class>
 </listener>
     

可替换地,   AbstractSecurityWebApplicationInitializer.enableHttpSessionEventPublisher()   可以归还。

我们可以知道为什么我们需要sessionManagement()maximumSessions(1),当然还需要expiredUrl("/login?expired")

  • 那么为什么需要antMatchers("/login**").permitAll()? 这样您就可以获得重定向到/login?expired的权限,否则您将被重定向到/login,因为anyRequest().authenticated()已应用当前HttpSecurity配置permitAll() /login/login?logout

2)如果您确实需要为我这样的特定用户访问当前登录用户或expireNow()特定会话,则可能需要getSessionRegistry(),但如果没有maximumSessions(1)则可以正常工作。< / p>

再次在doc:

的帮助下
  

使用SessionManagementConfigurer.maximumSessions(int)时,不要忘记   为应用程序配置HttpSessionEventPublisher以确保这一点   过期的会话被清理干净。在web.xml中,可以配置它   使用以下内容:

<listener>
      <listener-class>org.springframework.security.web.session.HttpSessionEventPublisher</listener-class>
 </listener>
     

可替换地,   AbstractSecurityWebApplicationInitializer.enableHttpSessionEventPublisher()   可以归还。

所以我应该在我的enableHttpSessionEventPublisher()课程中更改我的覆盖SecurityWebInitializer.java

public class SecurityWebInitializer extends AbstractSecurityWebApplicationInitializer {
    @Override
    protected boolean enableHttpSessionEventPublisher() {
        return true;
    }
}

3)现在我发现最后一件事这是我的问题:   由于我是Spring框架的新手,我学会了自定义UserDetails,但实际上有一点不好,但我可能会在以后做得更好,我创建了一个同时充当EntityUserDetails的实体:

    @Entity
    @Component("user")
    public class User implements UserDetails, Serializable {
        private static final long serialVersionUID = 1L;

        // ...

        @Override
        public boolean equals(Object obj) {
            if (obj instanceof User) {
              return username.equals( ((User) obj).getUsername() );
            }
            return false;
        }

        @Override
        public int hashCode() {
            return username != null ? username.hashCode() : 0;
        }
    }

我在论坛here中找到了一些建议,你应该实现两个hashCode() equals()方法,如果你看一下默认实现的源代码, UserDetails User.java你会发现它已经实现了两种方法,我做到了它并且它就像一个魅力。

这就是它,希望这有用。

您可能也想阅读此链接:Spring - Expiring all Sessions of a User

答案 1 :(得分:0)

我遇到了同样的问题,它起源于我的UserDetails实现:

ConcurrentSessionControlAuthenticationStrategy第93行:

final List<SessionInformation> sessions = sessionRegistry.getAllSessions(
        authentication.getPrincipal(), false);

SessionRegistryImpl第74行:

final Set<String> sessionsUsedByPrincipal = principals.get(principal);

if (sessionsUsedByPrincipal == null) {
    return Collections.emptyList();
}

在会话注册表中,在“原理”列表中搜索UserDetails对象。因此,您需要在UserDetails实现中重写equals和hashcode,否则,它将把它们视为单独的对象,因此始终返回一个emptyList。

示例:

public class ApplicationUser implements UserDetails {

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof ApplicationUser)) return false;
        ApplicationUser that = (ApplicationUser) o;
        return username.equals(that.username) &&
                email.equals(that.email) &&
                password.equals(that.password);
    }

    @Override
    public int hashCode() {
        return Objects.hash(username, email, password);
    }

}

答案 2 :(得分:0)

我在 Spring security 4.2.3 中使用了自定义的 AuthenticationFilter,这是我的解决方案(根据 documentation

此外,我遇到了与@Chris Avraam 解释的类似问题,我不得不重写我的 equals() 和 hashCode()

    @Bean
    public SessionRegistry sessionRegistry() {
        return new SessionRegistryImpl();
    }

    @Bean
    public CompositeSessionAuthenticationStrategy compositeSessionAuthenticationStrategy() {
        ArrayList<SessionAuthenticationStrategy> sessionAuthenticationStrategies =
                Lists.newArrayList(new ConcurrentSessionControlAuthenticationStrategy(sessionRegistry()),
                new RegisterSessionAuthenticationStrategy(sessionRegistry()));
        return new CompositeSessionAuthenticationStrategy(sessionAuthenticationStrategies);
    }

    @Bean
    public ApplicationAuthenticationFilter applicationAuthenticationFilter() throws IOException {
        ApplicationAuthenticationFilter applicationAuthenticationFilter = new ApplicationAuthenticationFilter();
        ...
        applicationAuthenticationFilter.setSessionAuthenticationStrategy(compositeSessionAuthenticationStrategy());
        return applicationAuthenticationFilter;
    }

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

    @Override
    public void configure(HttpSecurity http) throws Exception {
        ...
        http.sessionManagement().maximumSessions(1).sessionRegistry(sessionRegistry())
                .and().sessionAuthenticationStrategy(compositeSessionAuthenticationStrategy());
    }