`
aokunsang
  • 浏览: 812624 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

SpringSecurity3.1.2控制一个账户同时只能登录一次

阅读更多

网上看了很多资料,发现多多少少都有一些不足(至少我在使用的时候没成功),后来经过探索研究,得到解决方案。

 

具体SpringSecurity3怎么配置参考SpringSecurity3.1实践,这里只讲如何配置可以控制一个账户同时只能登录一次的配置实现。

 

网上很多配置是这样的,在<http>标签中加入concurrency-control配置,设置max-sessions=1。

<session-management invalid-session-url="/timeout">
  <concurrency-control max-sessions="1" error-if-maximum-exceeded="true"/>
</session-management>

 但是经测试一直没成功,经过一番查询原来是UsernamePasswordAuthenticationFilter中的一个默认属性sessionStrategy导致的。

首先我们在spring-security.xml中添加<debug/>,可以看到经过的过滤器如下:

Security filter chain: [
  ConcurrentSessionFilter --- (主要)并发控制过滤器,当配置<concurrency-control />时,会自动注册
  SecurityContextPersistenceFilter  --- 主要是持久化SecurityContext实例,也就是SpringSecurity的上下文。也就是SecurityContextHolder.getContext()这个东西,可以得到Authentication。
  LogoutFilter   ---  注销过滤器
  PmcUsernamePasswordAuthenticationFilter  --- (主要)登录过滤器,也就是配置文件中的<form-login/>配置、(本文主要问题就在这里)
  RequestCacheAwareFilter                 ---- 主要作用为:用户登录成功后,恢复被打断的请求(这些请求是保存在Cache中的)。这里被打断的请求只有出现AuthenticationException、AccessDeniedException两类异常时的请求。
  SecurityContextHolderAwareRequestFilter  ---- 不太了解
  AnonymousAuthenticationFilter        ------  匿名登录过滤器
  SessionManagementFilter              ----  session管理过滤器
  ExceptionTranslationFilter         ---- 异常处理过滤器(该过滤器只过滤下面俩拦截器)[处理的异常都是继承RuntimeException,并且它只处理AuthenticationException(认证异常)和AccessDeniedException(访问拒绝异常)]
  PmcFilterSecurityInterceptor       ------  自定义拦截器
  FilterSecurityInterceptor
]

 那么先来看UsernamePasswordAuthenticationFilter,它实际是执行其父类AbstractAuthenticationProcessingFilter的doFilter()方法,看部分源码:

try {
	    //子类继承该方法进行认证后返回Authentication
            authResult = attemptAuthentication(request, response);
            if (authResult == null) {
                // return immediately as subclass has indicated that it hasn't completed authentication
                return;
            }
	    //判断session是否过期、把当前用户放入session(这句是关键)
            sessionStrategy.onAuthentication(authResult, request, response);
        } catch(InternalAuthenticationServiceException failed) {
            logger.error("An internal error occurred while trying to authenticate the user.", failed);
            unsuccessfulAuthentication(request, response, failed);
            return;
        }

 看sessionStrategy的默认值是什么?

private SessionAuthenticationStrategy sessionStrategy = new NullAuthenticatedSessionStrategy();

 然后我们查询NullAuthenticatedSessionStrategy类的onAuthentication()方法竟然为空方法[问题就出现在这里]。那么为了解决这个问题,我们需要向UsernamePasswordAuthenticationFilter中注入类ConcurrentSessionControlStrategy。

这里需要说明下,注入的属性name名称为sessionAuthenticationStrategy,因为它的setter方法这么写的:

public void setSessionAuthenticationStrategy(SessionAuthenticationStrategy sessionStrategy) {
   this.sessionStrategy = sessionStrategy;
}

 这样,我们的配置文件需要这样配置(只贴出部分代码)[具体参考SpringSecurity3.1实践那篇博客]:

  <http entry-point-ref="loginAuthenticationEntryPoint">
  	<logout 
		delete-cookies="JSESSIONID"
  		logout-success-url="/" 
  		invalidate-session="true"/>
	
	<access-denied-handler error-page="/common/view/accessDenied.jsp"/>
		
	  	<session-management invalid-session-url="/timeout" session-authentication-strategy-ref="sas"/>
  	
  	<custom-filter ref="pmcLoginFilter" position="FORM_LOGIN_FILTER"/>
  	<custom-filter ref="concurrencyFilter" position="CONCURRENT_SESSION_FILTER"/>
  	<custom-filter ref="pmcSecurityFilter" before="FILTER_SECURITY_INTERCEPTOR"/>
  </http>


  <!-- 登录过滤器(相当于<form-login/>) -->
  <beans:bean id="pmcLoginFilter" class="org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter">
  	<beans:property name="authenticationManager" ref="authManager"></beans:property>
  	<beans:property name="authenticationFailureHandler" ref="failureHandler"></beans:property>
  	<beans:property name="authenticationSuccessHandler" ref="successHandler"></beans:property>
  	<beans:property name="sessionAuthenticationStrategy" ref="sas"></beans:property>
  </beans:bean>

  <!-- ConcurrentSessionFilter过滤器配置(主要设置账户session过期路径) -->
  <beans:bean id="concurrencyFilter" class="org.springframework.security.web.session.ConcurrentSessionFilter">
  	<beans:property name="expiredUrl" value="/timeout"></beans:property>
  	<beans:property name="sessionRegistry" ref="sessionRegistry"></beans:property>
  </beans:bean>

<!-- 未验证用户的登录入口 -->
  <beans:bean id="loginAuthenticationEntryPoint" class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">
  	<beans:constructor-arg name="loginFormUrl" value="/"></beans:constructor-arg>
  </beans:bean>

  <!-- 注入到UsernamePasswordAuthenticationFilter中,否则默认使用的是NullAuthenticatedSessionStrategy,则获取不到登录用户数
  error-if-maximum-exceeded:若当前maximumSessions为1,当设置为true表示同一账户登录会抛出SessionAuthenticationException异常,异常信息为:Maximum sessions of {0} for this principal exceeded;
  												                 当设置为false时,不会报错,则会让同一账户最先认证的session过期。
     具体参考:ConcurrentSessionControlStrategy:onAuthentication()
  -->
  <beans:bean id="sas" class="org.springframework.security.web.authentication.session.ConcurrentSessionControlStrategy">
  	<beans:property name="maximumSessions" value="1"></beans:property>
  	<beans:property name="exceptionIfMaximumExceeded" value="true"></beans:property>
  	<beans:constructor-arg name="sessionRegistry" ref="sessionRegistry"></beans:constructor-arg>
  </beans:bean>
  <beans:bean id="sessionRegistry" class="org.springframework.security.core.session.SessionRegistryImpl"></beans:bean>

 这样设置后,即可实现一个账户同时只能登录一次,但是有个小瑕疵。

如:A用户用账号admin登录系统,B用户在别处也用账号admin登录系统,这时会出现两种情况:

  1、设置exceptionIfMaximumExceeded=true,会报异常:SessionAuthenticationException("Maximum sessions of {0} for this principal exceeded");

  2、设置exceptionIfMaximumExceeded=false,那么B用户会把A用户挤掉,A用户再点击页面,则会跳转到

ConcurrentSessionFilter的expiredUrl路径。

     最理想的解决办法是第一种,但是不能让其报异常,在登录失败的handler中扑捉该异常,跳转到登录页面提示<该账号已经登录>。

     但是这里还会有点问题,当用户关闭浏览器或者直接关机等非正常退出时候将登录不进去。目前我的解决方案是:比对IP地址,判断如果是本机用户可以多次登录系统,然后使第一次登录的账号无效。大致思路关注2点:

1、把ConcurrentSessionControlStrategy源码copy一份,更改部分业务逻辑(主要拿客户端ip做文章);

2、重载SessionRegistryImpl,让其调用registerNewSession()方法时候保存ip地址。

详细的解决方案以及配置文件参考附件。

分享到:
评论
7 楼 syxfqw7 2016-08-05  
完全正确,补充一下

<beans:bean id="pmcLoginFilter" class="org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter"> 
    <beans:property name="authenticationManager" ref="zhfwAuthenticationManager"></beans:property> 
    <!--beans:property name="authenticationFailureHandler" ref="failureHandler"></beans:property> 
    <beans:property name="authenticationSuccessHandler" ref="successHandler"></beans:property--> 
    <beans:property name="sessionAuthenticationStrategy" ref="sas"></beans:property> 
</beans:bean>
可以这么写,handler不用可以注释掉
6 楼 MrwenQ 2014-09-12  
好的 谢谢
5 楼 aokunsang 2014-09-11  
MrwenQ 写道
楼主你好,我想问下 你这个同个IP多次登录是怎么做的呢?

主要做2点:
1、把ConcurrentSessionControlStrategy源码copy一份,更改部分业务逻辑(主要拿客户端ip做文章);
2、重载SessionRegistryImpl,让其调用registerNewSession()方法时候保存ip地址。
我把更改过的三个类和配置文件上传上去,你参考下吧。
4 楼 MrwenQ 2014-09-11  
楼主你好,我想问下 你这个同个IP多次登录是怎么做的呢?
3 楼 aokunsang 2014-08-15  
c.hle2008 写道
楼主,我按你弄的成功了,不过我的<beans:property name="exceptionIfMaximumExceeded" value="true"/>是这个配置,这样确实实现了只能一个用户登录,通过以下配置,也能正常退出到指定的页面,<logout logout-url="/user/logout.do" logout-success-url="/login.jsp" invalidate-session="true"/>,但是,如果再登录时,就登录不上了,提示“当前用户已经登录”,如何解决。

你是不是没有配置退出的地址?退出按钮的连接地址配置为:/user/logout.do应该就能安全退出,下次还能登陆。   如果是强制关闭页面的话,就有点棘手了。
2 楼 c.hle2008 2014-08-14  
楼主,我按你弄的成功了,不过我的<beans:property name="exceptionIfMaximumExceeded" value="true"/>是这个配置,这样确实实现了只能一个用户登录,通过以下配置,也能正常退出到指定的页面,<logout logout-url="/user/logout.do" logout-success-url="/login.jsp" invalidate-session="true"/>,但是,如果再登录时,就登录不上了,提示“当前用户已经登录”,如何解决。
1 楼 423418271 2014-01-17  
各种错误啊

相关推荐

    电信塔施工方案.doc

    5G通信行业、网络优化、通信工程建设资料。

    29-【智慧城市与政府治理分会场】10亿大数据助推都市治理-30页.pdf

    29-【智慧城市与政府治理分会场】10亿大数据助推都市治理-30页.pdf

    ABB IRC5 Compact 机器人产品手册

    ABB IRC5 Compact 机器人产品手册

    LTE容量优化高负荷小区优化指导书.docx

    5G通信行业、网络优化、通信工程建设资料

    施工工艺及质量检查记录表.docx

    5G通信行业、网络优化、通信工程建设资料。

    25G无源波分安装规范指导.docx

    5G通信、网络优化与通信建设

    基于Springboot+Vue在线宠物用品交易网站毕业源码案例设计.zip

    网络技术和计算机技术发展至今,已经拥有了深厚的理论基础,并在现实中进行了充分运用,尤其是基于计算机运行的软件更是受到各界的关注。加上现在人们已经步入信息时代,所以对于信息的宣传和管理就很关键。系统化是必要的,设计网上系统不仅会节约人力和管理成本,还会安全保存庞大的数据量,对于信息的维护和检索也不需要花费很多时间,非常的便利。 网上系统是在MySQL中建立数据表保存信息,运用SpringBoot框架和Java语言编写。并按照软件设计开发流程进行设计实现。系统具备友好性且功能完善。 网上系统在让售信息规范化的同时,也能及时通过数据输入的有效性规则检测出错误数据,让数据的录入达到准确性的目的,进而提升数据的可靠性,让系统数据的错误率降至最低。 关键词:vue;MySQL;SpringBoot框架 【引流】 Java、Python、Node.js、Spring Boot、Django、Express、MySQL、PostgreSQL、MongoDB、React、Angular、Vue、Bootstrap、Material-UI、Redis、Docker、Kubernetes

    通信各专业(管道、线路、设备)安全操作规程.docx

    5G通信行业、网络优化、通信工程建设资料

    node-v12.13.1-x86.msi

    Node.js,简称Node,是一个开源且跨平台的JavaScript运行时环境,它允许在浏览器外运行JavaScript代码。Node.js于2009年由Ryan Dahl创立,旨在创建高性能的Web服务器和网络应用程序。它基于Google Chrome的V8 JavaScript引擎,可以在Windows、Linux、Unix、Mac OS X等操作系统上运行。 Node.js的特点之一是事件驱动和非阻塞I/O模型,这使得它非常适合处理大量并发连接,从而在构建实时应用程序如在线游戏、聊天应用以及实时通讯服务时表现卓越。此外,Node.js使用了模块化的架构,通过npm(Node package manager,Node包管理器),社区成员可以共享和复用代码,极大地促进了Node.js生态系统的发展和扩张。 Node.js不仅用于服务器端开发。随着技术的发展,它也被用于构建工具链、开发桌面应用程序、物联网设备等。Node.js能够处理文件系统、操作数据库、处理网络请求等,因此,开发者可以用JavaScript编写全栈应用程序,这一点大大提高了开发效率和便捷性。 在实践中,许多大型企业和组织已经采用Node.js作为其Web应用程序的开发平台,如Netflix、PayPal和Walmart等。它们利用Node.js提高了应用性能,简化了开发流程,并且能更快地响应市场需求。

    C#Gif动画录制软件是一款方便好用的小软件源码.zip

    Gif动画录制软件是一款方便好用的小软件,使用此工具,您可以记录屏幕的选定区域,网络摄像头的实时提要或草图板上的实时图形。之后,您可以编辑动画并将其另存为gif,apng,视频,psd或png图像。

    协同供应链集成详细功能介绍V70.rar

    协同供应链集成(Collaborative Supply Chain Integration,CSCD是数字化转型解决方案中的重要一环,旨在通过技术手段实现供应链各环节的无缝对接与高效协作。V70版本作为该解决方案的一个升级版,可能包含了更加先进的功能和工具,以支持企业在复杂的商业环境中保持竞争力。这个资料包“协同供应链集成详细功能介绍V70.rar”很可能提供了对V70版本功能的深入剖析,包括了如何利用最新的数字化工具和技术来优化库存管理、订单处理、物流跟踪、供应商管理以及客户关系管理等关键供应链过程。它可能详细介绍了实时数据共享、自动化流程、预测分析、风险管理和智能决策支持系统等创新功能,这些功能有助于减少供应链中断,提高透明度,降低运营成本,并增强整个供应链网络的响应能力。通过这个资料,企业可以获得实施协同供应链集成的全面指导,了解如何通过集成的信息系统和平台,实现数据的一致性和准确性,以及如何通过合作伙伴之间的紧密协作,提升整个供应链的效率和灵活性。这对于追求数字化转型的企业来说,是一个宝贵的资源,因为它不仅提供了理论框架,还可能包含了实际案例研究、最佳实践和实施策略,帮助企业

    英飞凌官方ADS库1.9.20版

    英飞凌官方ADS库1.9.20版

    node-v7.7.3.tar.xz

    Node.js,简称Node,是一个开源且跨平台的JavaScript运行时环境,它允许在浏览器外运行JavaScript代码。Node.js于2009年由Ryan Dahl创立,旨在创建高性能的Web服务器和网络应用程序。它基于Google Chrome的V8 JavaScript引擎,可以在Windows、Linux、Unix、Mac OS X等操作系统上运行。 Node.js的特点之一是事件驱动和非阻塞I/O模型,这使得它非常适合处理大量并发连接,从而在构建实时应用程序如在线游戏、聊天应用以及实时通讯服务时表现卓越。此外,Node.js使用了模块化的架构,通过npm(Node package manager,Node包管理器),社区成员可以共享和复用代码,极大地促进了Node.js生态系统的发展和扩张。 Node.js不仅用于服务器端开发。随着技术的发展,它也被用于构建工具链、开发桌面应用程序、物联网设备等。Node.js能够处理文件系统、操作数据库、处理网络请求等,因此,开发者可以用JavaScript编写全栈应用程序,这一点大大提高了开发效率和便捷性。 在实践中,许多大型企业和组织已经采用Node.js作为其Web应用程序的开发平台,如Netflix、PayPal和Walmart等。它们利用Node.js提高了应用性能,简化了开发流程,并且能更快地响应市场需求。

    node-v11.1.0-linux-armv7l.tar.gz

    Node.js,简称Node,是一个开源且跨平台的JavaScript运行时环境,它允许在浏览器外运行JavaScript代码。Node.js于2009年由Ryan Dahl创立,旨在创建高性能的Web服务器和网络应用程序。它基于Google Chrome的V8 JavaScript引擎,可以在Windows、Linux、Unix、Mac OS X等操作系统上运行。 Node.js的特点之一是事件驱动和非阻塞I/O模型,这使得它非常适合处理大量并发连接,从而在构建实时应用程序如在线游戏、聊天应用以及实时通讯服务时表现卓越。此外,Node.js使用了模块化的架构,通过npm(Node package manager,Node包管理器),社区成员可以共享和复用代码,极大地促进了Node.js生态系统的发展和扩张。 Node.js不仅用于服务器端开发。随着技术的发展,它也被用于构建工具链、开发桌面应用程序、物联网设备等。Node.js能够处理文件系统、操作数据库、处理网络请求等,因此,开发者可以用JavaScript编写全栈应用程序,这一点大大提高了开发效率和便捷性。 在实践中,许多大型企业和组织已经采用Node.js作为其Web应用程序的开发平台,如Netflix、PayPal和Walmart等。它们利用Node.js提高了应用性能,简化了开发流程,并且能更快地响应市场需求。

    通信工程质量控制方案.docx

    5G通信行业、网络优化、通信工程建设资料。

    5GC参数设置问题导致UE无法接入.docx

    5G通信行业、网络优化、通信工程建设资料

    动力机房--低压市电、基础勘察设计知识.pptx

    5G通信行业、网络优化、通信工程建设资料

    jump(1).mobileprovision

    jump(1).mobileprovision

    node-v6.14.3-aix-ppc64.tar.gz

    Node.js,简称Node,是一个开源且跨平台的JavaScript运行时环境,它允许在浏览器外运行JavaScript代码。Node.js于2009年由Ryan Dahl创立,旨在创建高性能的Web服务器和网络应用程序。它基于Google Chrome的V8 JavaScript引擎,可以在Windows、Linux、Unix、Mac OS X等操作系统上运行。 Node.js的特点之一是事件驱动和非阻塞I/O模型,这使得它非常适合处理大量并发连接,从而在构建实时应用程序如在线游戏、聊天应用以及实时通讯服务时表现卓越。此外,Node.js使用了模块化的架构,通过npm(Node package manager,Node包管理器),社区成员可以共享和复用代码,极大地促进了Node.js生态系统的发展和扩张。 Node.js不仅用于服务器端开发。随着技术的发展,它也被用于构建工具链、开发桌面应用程序、物联网设备等。Node.js能够处理文件系统、操作数据库、处理网络请求等,因此,开发者可以用JavaScript编写全栈应用程序,这一点大大提高了开发效率和便捷性。 在实践中,许多大型企业和组织已经采用Node.js作为其Web应用程序的开发平台,如Netflix、PayPal和Walmart等。它们利用Node.js提高了应用性能,简化了开发流程,并且能更快地响应市场需求。

    Exam2024SpringA.ipynb

    Exam2024SpringA.ipynb

Global site tag (gtag.js) - Google Analytics