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

需求

  1. 同一账号只允许在一个设备登录
  2. 上一个登录的设备状态自动失效。(刷新后回到登录页面)

分析

  1. 经过查资料已知:SpringSecuritySession管理这块默认支持此功能。
  2. 但是项目自定义了登录过滤器,继承AbstractAuthenticationProcessingFilter替换了默认的UsernamePasswordAuthenticationFilter
  3. 会话管理是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负责把上面两个打包执行。

  1. 所以我们自定义的认证过滤器,直接注入CompositeSessionAuthenticationStrategy即可。

分支剧情:(有的人说需要重写,我场景中没重写正常,所以就跳过了。此段未验证,官方文档也没提)session注册管理这块由SessionRegistry负责,默认实现叫SessionRegistryImpl
UserDetails默认实现类org.springframework.security.core.userdetails.User 重写了 toStringequalshashcode

  1. 添加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 - 学习笔记 - 会话管理之并发控制:同一账号只允许在一个设备登录

SpringSecurity - 学习笔记 - 会话管理之并发控制:同一账号只允许在一个设备登录

史上最全最精简的学习路线图!王者笔记!

字节大神强推千页PDF学习笔记,成功定级腾讯T3-2

Spring Security 架构原理学习笔记

Apache Shiro学习笔记总结