我希望在用户超过maxSession计数时阻止登录。例如,每个用户都可以登录一次。然后,如果登录用户尝试另一个登录系统应该禁用他的登录。
.sessionManagement()
.maximumSessions(1).expiredUrl("/login?expire").maxSessionsPreventsLogin(true)
.sessionRegistry(sessionRegistry());
@Bean
public static ServletListenerRegistrationBean httpSessionEventPublisher() {
return new ServletListenerRegistrationBean(new HttpSessionEventPublisher());
}
答案 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,但实际上有一点不好,但我可能会在以后做得更好,我创建了一个同时充当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
答案 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());
}