spring security使用自定义的的AuthenticationFilter时,要限制session个数,禁止多端同时登录
在WebSecurityConfigurerAdapter#configure(HttpSecurity)方法中配置session管理没有效果,因为我们使用了自己的AuthenticationFilter,只能手动给LoginFilter配置SessionAuthenticationStrategy。
@Configuration
public class CustomFilterSecurityConfig extends WebSecurityConfigurerAdapter {
//sessesion失效所需要的监听器,spring security默认配置过个bean,我们只需要自动注入即可
@Autowired
DelegatingApplicationListener delegatingApplicationListener;
//省略其他......
@Bean
public LoginFilter loginFilter() throws Exception {
LoginFilter loginFilter = new LoginFilter();
//省略其他......
//自定义filter要手动配置SessionAuthenticationStrategy
loginFilter.setSessionAuthenticationStrategy(sessionAuthenticationStrategy());
return loginFilter;
}
@Bean
public SessionAuthenticationStrategy sessionAuthenticationStrategy() {
SessionRegistryImpl sessionRegistry = new SessionRegistryImpl();
ConcurrentSessionControlAuthenticationStrategy concurrentSessionControlStrategy =
new ConcurrentSessionControlAuthenticationStrategy(sessionRegistry);
delegatingApplicationListener.addListener(new GenericApplicationListenerAdapter(sessionRegistry));
concurrentSessionControlStrategy.setMaximumSessions(1);
//除非先在以前的客户端登出,否则不能在新的客户端登录
concurrentSessionControlStrategy.setExceptionIfMaximumExceeded(true);
//要注意这3个实例在集合中的顺序
CompositeSessionAuthenticationStrategy delegateStrategies = new CompositeSessionAuthenticationStrategy(
Arrays.asList(concurrentSessionControlStrategy,
new ChangeSessionIdAuthenticationStrategy(),
new RegisterSessionAuthenticationStrategy(sessionRegistry)
));
return delegateStrategies;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
//替代默认的UsernamePasswordAuthenticationFilter
http.addFilterAt(loginFilter(), UsernamePasswordAuthenticationFilter.class);
http.authorizeRequests()
//这里的配置session管理没用,因为我们使用了自己的AuthenticationFilter
.sessionManagement()
.maximumSessions(1)
.maxSessionsPreventsLogin(true)
;
}
}
SessionAuthenticationStrategy
是一个接口,我们不能只使用ConcurrentSessionControlAuthenticationStrategy
做session管理,而应该使用CompositeSessionAuthenticationStrategy
实例。CompositeSessionAuthenticationStrategy
将ConcurrentSessionControlAuthenticationStrategy
、 ChangeSessionIdAuthenticationStrategy
、RegisterSessionAuthenticationStrategy
组合起来,使用了典型的委托组合设计模式(或代理模式),CompositeSessionAuthenticationStrategy
在onAuthentication方法中循环调用这三个实例它们自己的onAuthentication.
这里的RegisterSessionAuthenticationStrategy
做session注册的,它比较重要,若没有它就无法检测登录的session次数。
我的SessionAuthenticationStrategy配置也是参考配置类方法SessionManagementConfigurer#getSessionAuthenticationStrategy来实现的。可以看出阅读源码是多么重要吧!
private SessionAuthenticationStrategy getSessionAuthenticationStrategy(H http) {
if (this.sessionAuthenticationStrategy != null) {
return this.sessionAuthenticationStrategy;
}
List<SessionAuthenticationStrategy> delegateStrategies = this.sessionAuthenticationStrategies;
SessionAuthenticationStrategy defaultSessionAuthenticationStrategy;
if (this.providedSessionAuthenticationStrategy == null) {
// If the user did not provide a SessionAuthenticationStrategy
// then default to sessionFixationAuthenticationStrategy
//执行这个分支defaultSessionAuthenticationStrategy是一个ChangeSessionIdAuthenticationStrategy实例
defaultSessionAuthenticationStrategy = postProcess(this.sessionFixationAuthenticationStrategy);
}
else {
defaultSessionAuthenticationStrategy = this.providedSessionAuthenticationStrategy;
}
if (isConcurrentSessionControlEnabled()) {
//配置ConcurrentSessionControlAuthenticationStrategy
SessionRegistry sessionRegistry = getSessionRegistry(http);
ConcurrentSessionControlAuthenticationStrategy concurrentSessionControlStrategy = new ConcurrentSessionControlAuthenticationStrategy(
sessionRegistry);
concurrentSessionControlStrategy.setMaximumSessions(this.maximumSessions);
concurrentSessionControlStrategy.setExceptionIfMaximumExceeded(this.maxSessionsPreventsLogin);
concurrentSessionControlStrategy = postProcess(concurrentSessionControlStrategy);
//配置RegisterSessionAuthenticationStrategy
RegisterSessionAuthenticationStrategy registerSessionStrategy = new RegisterSessionAuthenticationStrategy(
sessionRegistry);
registerSessionStrategy = postProcess(registerSessionStrategy);
delegateStrategies.addAll(Arrays.asList(concurrentSessionControlStrategy,
defaultSessionAuthenticationStrategy, registerSessionStrategy));
}
else {
delegateStrategies.add(defaultSessionAuthenticationStrategy);
}
//将上面3个SessionAuthenticationStrategy传入CompositeSessionAuthenticationStrategy的构造方法
this.sessionAuthenticationStrategy = postProcess(
new CompositeSessionAuthenticationStrategy(delegateStrategies));
return this.sessionAuthenticationStrategy;
}