Spring Security maxSession не работает

Я хочу предотвратить вход в систему, когда пользователь превышает количество сеансов maxSession. Например, каждый пользователь может войти один раз. И затем, если зарегистрированный пользователь попробует другую систему входа в систему, она должна отключить для него вход в систему.

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


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

person fatiherdem    schedule 18.06.2016    source источник


Ответы (3)


ПРИМЕЧАНИЕ. Это проверено на Spring MVC и 4.3.9.RELEASE, я еще не использовал Spring Boot.

Я нашел решение, позвольте мне поделиться, как оно сработало со мной.

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» без выхода из системы и предпринимается попытка аутентификации с «user», первый сеанс будет принудительно завершен и отправлен на URL-адрес «/login?expired».

@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.

Поэтому я должен изменить свое переопределение enableHttpSessionEventPublisher() в моем классе SecurityWebInitializer.java:

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

3) Теперь еще одна последняя вещь, которую я обнаружил это была моя проблема: поскольку я новичок в Spring framework, я научился делать пользовательские 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;
        }
    }

Несколько лет назад я нашел на форуме здесь, что вы должны реализовать оба метода hashCode() equals(), и если вы посмотрите на исходный код реализации по умолчанию для UserDetails User.java вы найдете что в нем реализованы оба метода, я сделал это, и это сработало как шарм.

Вот и все, надеюсь, что это полезно.

Вы также можете прочитать эту ссылку: Spring - Истечение срока действия всех сеансов пользователя

person Ahmed Shendy    schedule 21.08.2017

У меня была та же проблема, и она возникла в моей реализации 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. Поэтому вам нужно переопределить equals и hashcode в вашей реализации UserDetails, иначе он будет рассматривать их как отдельные объекты и, таким образом, всегда будет возвращать 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);
    }

}
person Chris Avraam    schedule 23.07.2020
comment
хорошее решение, которое сработало для меня. Спасибо! Требование hashCode должно быть более четким во многих примерах и документах :( - person Znheb; 21.07.2021

Я использовал собственный AuthenticationFilter в безопасности Spring 4.2.3, и это мое решение (согласно документация)

Кроме того, у меня была аналогичная проблема, как объяснил @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());
    }

person hillel_guy    schedule 19.01.2021