Spring Security maxSession 不起作用

标签 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());
}

最佳答案

注意 : 这个是在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()

Example Configuration

The following configuration demonstrates how to enforce that only a single instance of a user is authenticated at a time. If a user authenticates with the username "user" without logging out and an attempt to authenticate with "user" is made the first session will be forcibly terminated and sent to the "/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");
    }  }   

When using SessionManagementConfigurer.maximumSessions(int), do not forget to configure HttpSessionEventPublisher for the application to ensure that expired sessions are cleaned up. In a web.xml this can be configured using the following:

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

Alternatively, AbstractSecurityWebApplicationInitializer.enableHttpSessionEventPublisher() could return true.



我们可以知道为什么我们需要 sessionManagement() , maximumSessions(1) ,当然还有 expiredUrl("/login?expired") .
  • 那么为什么需要antMatchers("/login**").permitAll() ?
    这样你就可以有权限被重定向到 /login?expired , 否则你将被重定向到 /login因为 anyRequest().authenticated() , 与电流 HttpSecurity配置permitAll()适用于/login/login?logout .

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

    所以再次在文档的帮助下:

    When using SessionManagementConfigurer.maximumSessions(int), do not forget to configure HttpSessionEventPublisher for the application to ensure that expired sessions are cleaned up. In a web.xml this can be configured using the following:

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

    Alternatively, AbstractSecurityWebApplicationInitializer.enableHttpSessionEventPublisher() could return true.



    所以我应该改变我的覆盖 enableHttpSessionEventPublisher()在我的 SecurityWebInitializer.java类(class):
    public class SecurityWebInitializer extends AbstractSecurityWebApplicationInitializer {
        @Override
        protected boolean enableHttpSessionEventPublisher() {
            return true;
        }
    }
    

    3)现在我发现的最后一件事这是我的问题 :
    由于我是 Spring 框架的新手,我学会了自定义 UserDetails,但实现起来不太好,但我以后可能会做得更好,我创建了一个同时充当 Entity 的实体。和一个 UserDetails :
        @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

    关于Spring Security maxSession 不起作用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37892563/

    相关文章:

    java - 从 Spring Session 中的 SessionDestroyedEvent 获取安全上下文

    java - 在 Spring Boot 中处理数据库

    spring - 使用 Spring 将重定向设置为自定义身份验证失败处理程序

    java - 有什么方法可以在 Spring 中使用 Swagger 包含 Controller 中未使用的类吗?

    spring - Grails:在某些路径上禁用 Spring Security Core

    spring - 如何在 Spring Security 6 中自定义 expressionHandler 和 accessDecisionManager

    spring-security - org.opensaml.common.SAMLException : Response has invalid status code urn:oasis:names:tc:SAML:2. 0 :status:Responder, 状态消息为空

    java - 将 JSON 嵌套到 POJO

    java - 使用 Redis Sentinel 正确配置 @EnableRedisHttpSession

    spring - 是否可以在没有 Redis 的情况下使用 Spring Boot session ?