【问题标题】:Spring Security maxSession doesn't workSpring Security maxSession 不起作用
【发布时间】:2016-06-18 01:55:56
【问题描述】:

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

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


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

【问题讨论】:

    标签: spring spring-security spring-session


    【解决方案1】:

    注意:这是在Spring MVC4.3.9.RELEASE上测试的,我还没用过Spring Boot。 p>

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

    1) 我将 HttpSecurity 与 SessionManagement 配置如下:

    @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”进行身份验证,并且 尝试使用“用户”进行身份验证,第一个会话将是 强制终止并发送到“/login?expired” 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() 可以返回 true。

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

    • 那么为什么需要antMatchers("/login**").permitAll()? 这样你就可以被重定向到/login?expired,否则你将被重定向到/login,因为anyRequest().authenticated(),当前HttpSecurity配置permitAll()应用于/login/login?logout。李>

    2) 如果您确实需要访问当前登录用户或expireNow() 特定用户的特定会话,您可能需要getSessionRegistry(),但没有它maximumSessions(1) 工作正常。

    再次在文档的帮助下:

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

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

    或者, AbstractSecurityWebApplicationInitializer.enableHttpSessionEventPublisher() 可以返回 true。

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

    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

    【讨论】:

      【解决方案2】:

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

      ConcurrentSessionControlAuthenticationStrategy 第 93 行:

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

      SessionRegistryImpl 第 64 行:

      final Set<String> sessionsUsedByPrincipal = principals.get(principal);
      
      if (sessionsUsedByPrincipal == null) {
          return Collections.emptyList();
      }
      

      在会话注册表中,在“主体”列表中搜索 UserDetails 对象。因此,您需要在 UserDetails 实现中覆盖 equalshashcode,否则,它将把它们视为单独的对象,因此总是返回一个空列表。

      例子:

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

      【讨论】:

      • 对我有用的好解决方案。谢谢!在许多示例和文档中,“hashCode”要求应该更清楚:(
      【解决方案3】:

      我在 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());
          }
      
      

      【讨论】:

        猜你喜欢
        • 2012-01-02
        • 2017-06-08
        • 2018-06-26
        • 2015-08-27
        • 2013-04-28
        • 2016-02-16
        • 2018-02-03
        • 2021-06-20
        • 2015-07-29
        相关资源
        最近更新 更多