注意:这是在Spring MVC和4.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,但实现起来有点不太好,但我以后可能会做得更好,我创建了一个同时充当 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