SpringSecurity - 学习笔记 - 会话管理之并发控制:同一账号只允许在一个设备登录
Posted 笑虾
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringSecurity - 学习笔记 - 会话管理之并发控制:同一账号只允许在一个设备登录相关的知识,希望对你有一定的参考价值。
SpringSecurity - 学习笔记 - 会话管理之并发控制:同一账号只允许在一个设备登录
场景
接手一个老项目:
先上pom.xml
...略
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>4.2.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>4.2.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.2.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>4.2.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>4.2.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>4.2.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>4.2.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>4.2.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>4.2.4.RELEASE</version>
</dependency>
<!-- security -->
<!-- Spring Security -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
<version>4.2.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>4.2.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>4.2.2.RELEASE</version>
</dependency>
...略
需求
- 同一账号只允许在一个设备登录
- 上一个登录的设备状态自动失效。(刷新后回到登录页面)
分析
- 经过查资料已知:SpringSecurity 的Session管理这块默认支持此功能。
- 但是项目自定义了登录过滤器,继承
AbstractAuthenticationProcessingFilter
替换了默认的UsernamePasswordAuthenticationFilter
。 - 会话管理是
AbstractAuthenticationProcessingFilter
中通过注入的sessionStrategy
来实现的,但默认实现只是个摆设,我们需要自己来注入。
public abstract class AbstractAuthenticationProcessingFilter extends GenericFilterBean implements ApplicationEventPublisherAware, MessageSourceAware
// 略...
private SessionAuthenticationStrategy sessionStrategy = new NullAuthenticatedSessionStrategy();
// 略...
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException
try
authResult = attemptAuthentication(request, response);
if (authResult == null)
return;
// 这里处理会话。
sessionStrategy.onAuthentication(authResult, request, response);
// 略...
3.1. SpringSecurity自带的 ConcurrentSessionControlAuthenticationStrategy
就可以实现判断会话是否超限。但坑在解耦的彻底,它只管这 核心业务。至于谁来统计Session
完全不鸟,所以在sessionRegistry.getAllSessions()
这一步就永远是空的。自然怎么都超不了。
3.2. SpringSecurity自带的RegisterSessionAuthenticationStrategy
负责(指挥)注册管理。
3.3. SpringSecurity自带的CompositeSessionAuthenticationStrategy
负责把上面两个打包执行。
- 所以我们自定义的认证过滤器,直接注入
CompositeSessionAuthenticationStrategy
即可。
分支剧情:(有的人说需要重写,我场景中没重写正常,所以就跳过了。此段未验证,官方文档也没提)session注册管理这块由
SessionRegistry
负责,默认实现叫SessionRegistryImpl
。
UserDetails
默认实现类org.springframework.security.core.userdetails.User
重写了toString
、equals
、hashcode
。
- 添加
concurrentSessionFilter
踢掉之前的登录设备。(上面的工作只是把旧session
标为过期,但并不会做其它处理。加这上个过滤器,旧设备一有请求过来,它判断到session
过期就直接重定向到登录去了。)
配置
web.xml
想使用并发会话,需要注册这个监听器。它负责监听并发布 Session 事件
<listener>
<listener-class>
org.springframework.security.web.session.HttpSessionEventPublisher
</listener-class>
</listener>
application-security.xml
SpringSecurity
配置文件,有的项目可能取名不同。这段基本就是官网搬的,根据自己情况稍微调整一下。
<http auto-config="true" use-expressions="true">
<form-login login-page="/login.jsp" />
<logout logout-success-url="/logout" />
<logout invalidate-session="true" logout-url="/logout" logout-success-url="/login.jsp" />
<!-- a1. 自定义认证过滤器 -->
<custom-filter ref="myAuthenticationFilter" before="FORM_LOGIN_FILTER" />
<custom-filter ref="springSecurityFilterPermission" before="FILTER_SECURITY_INTERCEPTOR" />
<!-- b1. 处理超过并发的过滤器 -->
<custom-filter ref="concurrentSessionFilter" position="CONCURRENT_SESSION_FILTER" />
<csrf disabled="true" />
<headers><frame-options policy="SAMEORIGIN" /></headers>
</http>
<!-- a2. 自定义认证过滤器 -->
<beans:bean id="myAuthenticationFilter"
class="com.faith.framework.core.web.security.RequestHeaderProcessingFilter">
<beans:property name="authenticationManager" ref="authenticationManager" />
<beans:property name="logoutURL" value="/logout" />
<beans:property name="loginFailureHandler" ref="loginFailureHandler" />
<beans:property name="loginSuccessHandler" ref="loginSuccessHandler" />
<beans:property name="loginPageURL" value="/login" />
<beans:property name="loginFailPageURL" value="/logout" />
<!-- a3. 会话管理由此去 -->
<beans:property name="sessionAuthenticationStrategy" ref="sas"/>
</beans:bean>
<!--Spring Security 一个账号同一时刻只能登录一次 begin-->
<!--a4. 负责把 a4.1、a4.2. 打包执行。我们自定义将这个bean注给 AbstractAuthenticationProcessingFilter.sessionStrategy-->
<beans:bean id="sas"
class="org.springframework.security.web.authentication.session.CompositeSessionAuthenticationStrategy">
<beans:constructor-arg>
<beans:list>
<beans:ref bean="concurrentSessionControlAuthenticationStrategy"/>
<beans:ref bean="registerSessionAuthenticationStrategy"/>
</beans:list>
</beans:constructor-arg>
</beans:bean>
<!-- a4.1. sessionRegistry 获取/登记会话。它有一个 Map<String, SessionInformation> sessionIds 来管理会话。 -->
<beans:bean id="sessionRegistry" class="org.springframework.security.core.session.SessionRegistryImpl" />
<!-- a4.2. 负责判断当前用户的会话数量是否超过上限-->
<beans:bean id="concurrentSessionControlAuthenticationStrategy"
class="org.springframework.security.web.authentication.session.ConcurrentSessionControlAuthenticationStrategy">
<beans:constructor-arg name="sessionRegistry" ref="sessionRegistry" />
<beans:property name="maximumSessions" value="1" /> <!-- 最多只能在一个设备登录 -->
<beans:property name="exceptionIfMaximumExceeded" value="false" /> <!-- 超过时,踢前一个设备下线 -->
</beans:bean>
<!-- 负责登记session-->
<beans:bean id="registerSessionAuthenticationStrategy"
class="org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy">
<beans:constructor-arg name="sessionRegistry" ref="sessionRegistry" />
</beans:bean>
<!-- b2. ConcurrentSessionFilter 踢超过并发的会话下线 -->
<beans:bean id="concurrentSessionFilter" class="org.springframework.security.web.session.ConcurrentSessionFilter">
<beans:constructor-arg name="sessionRegistry" ref="sessionRegistry" />
<beans:constructor-arg name="sessionInformationExpiredStrategy" ref="simpleRedirectSessionInformationExpiredStrategy" />
</beans:bean>
<beans:bean id="simpleRedirectSessionInformationExpiredStrategy" class="org.springframework.security.web.session.SimpleRedirectSessionInformationExpiredStrategy">
<beans:constructor-arg name="invalidSessionUrl" value="/login.jsp" />
</beans:bean>
<!--Spring Security 一个账号同一时刻只能登录一次 end-->
参考消息
官方文档:IV. Web Application Security》21. Session Management 》21.3 Concurrency Control
中文文档:Part IV. Web 应用程序安全》会话管理 》21.3 并发控制
Spring Security实现禁止用户重复登陆的配置原理
【Spring Security】如何实现多设备同一时间只允许一个账号登录(即前登录用户被后登录用户挤下线)?只需简单两步!
以上是关于SpringSecurity - 学习笔记 - 会话管理之并发控制:同一账号只允许在一个设备登录的主要内容,如果未能解决你的问题,请参考以下文章
SpringSecurity - 学习笔记 - 会话管理之并发控制:同一账号只允许在一个设备登录