Spring Security maxSession不起作用

fat*_*dem 5 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());
}
Run Code Online (Sandbox Code Playgroud)

小智 6

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

ConcurrentSessionControlAuthenticationStrategy 第 93 行:

final List<SessionInformation> sessions = sessionRegistry.getAllSessions(
        authentication.getPrincipal(), false);
Run Code Online (Sandbox Code Playgroud)

SessionRegistryImpl 第 64 行:

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

if (sessionsUsedByPrincipal == null) {
    return Collections.emptyList();
}
Run Code Online (Sandbox Code Playgroud)

在会话注册表中,在“主体”列表中搜索 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);
    }

}
Run Code Online (Sandbox Code Playgroud)


Ahm*_*ndy 5

注意:这已经在Spring MVC4.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
    ;           
}
Run Code Online (Sandbox Code Playgroud)

借助文档Spring Doc> HttpSecurity> sessionManagement()

配置示例

以下配置演示了如何强制一次仅认证一个用户实例。如果用户未注销而使用用户名“ user”进行身份验证,并且尝试使用“ 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");
    }  }   
Run Code Online (Sandbox Code Playgroud)

使用SessionManagementConfigurer.maximumSessions(int)时,请不要忘记为应用程序配置HttpSessionEventPublisher,以确保清除过期的会话。在web.xml中,可以使用以下命令进行配置:

<listener>
      <listener-class>org.springframework.security.web.session.HttpSessionEventPublisher</listener-class>
 </listener>
Run Code Online (Sandbox Code Playgroud)

或者,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>
Run Code Online (Sandbox Code Playgroud)

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

因此,我应该enableHttpSessionEventPublisher()SecurityWebInitializer.java课堂上更改我的替代:

public class SecurityWebInitializer extends AbstractSecurityWebApplicationInitializer {
    @Override
    protected boolean enableHttpSessionEventPublisher() {
        return true;
    }
}
Run Code Online (Sandbox Code Playgroud)

3)现在,我发现的最后一件事是我的问题:由于我是Spring框架的新手,我学会了自定义UserDetails,但是实现起来有些差,但是以后可能会做得更好,我创建了一个实体作为an Entity和a 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;
        }
    }
Run Code Online (Sandbox Code Playgroud)

我发现有人建议多年前的论坛在这里,你应该同时实现hashCode() equals()的方法,如果你在对的UserDetails默认实现的源代码,您或许User.java你会发现,它已经实施了这两种方法,我做到了,它的工作像个魅力。

就是这样,希望对您有所帮助。

您可能也想阅读此链接:Spring-用户的所有会话期满