分享

Session 的管理和并发

 windli笔记 2013-03-06

Spring Security 的一个常见配置就是检测相同的用户以不同的 session 登录安全系统。这被称为并发控制( concurrency control ,是 session 管理( session management 一系列相关配置功能的一部分。严格来说,这个功能并不是高级配置,但是它会让很多新手感到迷惑,并且最好在你对 Sping Security 整体功能有所了解的基础上再掌握它。 Spring Security session 管理能够以两种不同的方式进行配置—— session 固化保护( session fixation protection )和并发控制。因为并发控制的功能基于 session 固化保护所提供的框架,我们先介绍 session 固化。

配置session fixation 防护

         如果我们使用的是 security 命名空间的配置方式, session 固化防护已经被默认进行了配置。如果我们要指明将其配置为与默认设置一致的话,我们需要这样:

 

Xml代码  收藏代码
  1. < http   auto-config = "true"   use-s = "true" >    
  2.   <!-- ... -->   
  3.   < session-management   session-fixation-protection = "migrateSession" />   
  4. </ http >   

 Session 固化防护这个功能你可能并不会在意,除非你想扮演一个恶意的用户。我们将向你展示如何模拟一个 session 窃取攻击,但是在此之前,有必要理解 session 固化是怎么回事以及怎样防止这样的攻击。

理解 session fixation 攻击

         Session 固化是恶意用户试图窃取系统中一个未认证用户的 session 。对攻击者来说,可以通过各种技术来获取用户 session 的唯一标识(例如, JSESSIONID )。如果攻击者创建了带有用户 JSESSIONID cookie 或者 URL 参数,他就能够访问用户的 session

         尽管这是一个明显的问题,但是一般情况下,如果用户没有经过认证,他们就还没有输入任何敏感信息(假设站点的安全已经正确规划了)。如果用户认证后依旧使用相同的 session 标识符,这个问题就会比较更加重要了。如果用户在认证后还使用相同的标识符,那攻击者现在就能访问认证过用户的 session ,而甚至不必要知道他们的用户名和密码。

         【此时,你可能很不屑并认为在现实世界中这不会发生。实际上, session 窃取攻击经常发生。关于这个话题,我们建议(正如在第三章那样)你花些时间阅读由 OWASP 组织( http://www./ )发布的包含重要信息的文章以及学习案例。攻击者和恶意用户是真实存在的,如果你不了解他们常用的技术及如何避免,他们会对你的用户、应用或公司造成真正的损害。】

下图展现了 session 固化攻击是如何发生的:



 既然了解了攻击如何进行,接下来我们查看 Spring Security 如何防止。

使用 Spring Security 防止 session fixation 攻击

如果我们能够阻止用户在认证前和认证后使用相同的 session ,我们就能够让攻击者掌握的 session ID 信息变得没有用处。 Spring Security session 固化防护解决这个问题的方式就是在用户认证之后明确创建一个新的 session 并将旧的 session 失效。

让我们看下图:



 我们可以看到一个新的过滤器, o.s.s.web.session.SessionManagementFilter ,负责检查一个特定的用户是否为新认证的。如果用户是新认证的,一个配置的 o.s.s.web.authentication.session.SessionAuthenticationStrategy 将确定要怎样做。 o.s.s.web.authentication.session.SessionAuthenticationStrategy 将会创建一个新的 session (如果用户已经拥有一个的话),并将已存在 session 的内容拷贝到新 session 中去。这看起来很简单,但是,通过上面的图表我们可以看到,它能够有效组织恶意用户在未知用户登录后重用 session ID

模拟 session fixation 攻击

此时,你可能会想要看一下在模拟 session 固化攻击时会涉及到什么。为了实现这一点,你需要在 dogstore-security.xml 中配置 session 固化防护失效。

 

Xml代码  收藏代码
  1. < session-management   session-fixation-protection = "none" />   

接下来,你需要打开两个浏览器。我们将会在 IE 中初始化 session ,并从那里窃取,我们的攻击者将会使用窃取到的 session Firefox 中登录。我们将会使用 Internet Explorer Developer Tools IE 8 中自带)以及 Firefox Web Developer Add-On (第三章中已经给过 URL )来查看和控制 cookie

          IE 中打开 JBCP Pets 首页,然后打开开发者工具(看“工具”下拉菜单或点击 F12 ),并在“缓存”菜单下选择“查看 Cookie 信息”。在合适域下(如果使用 localhost 将为空)找到 JSESSIONID cookie


          session cookie 的值复制到粘贴板上,然后登录 JBCP Pets 站点。如果你重复“查看 Cookie 信息”,你将会发现 JSESSIONID 在登录后没有变化,这将会导致很容易受到 session 固化攻击。

          Firefox 下,打开 JBCP Pets 站点。你将会被分配一个 session cookie ,这能通过 Cookie 菜单的“查看 Cookie 信息”菜单项查看到。

 



 为了完成我们的攻击,我们点击“ Edit Cookie ”选项,并将从 IE 中复制到粘贴板上的 JSESSIONID 值粘贴进来,如下图所示:



 我们的 session 固化攻击完成了!如果此时在 Firefox 中重新加载页面,你将以 IE 中已登录用户相同的身份进入系统,并不需要你知道用户名和密码。你是否害怕恶意用户了?

现在,重新使 session 固化防护生效然后重新尝试这个练习。你会发现,在这种情况下, JSESSIONID 在用户登录后会发生变化。因为你已经了解了 session 固化攻击是如何发生的,这意味着减少了可信任用户陷入这种攻击的风险。干的漂亮!

细心的开发人员应该会注意到有很多种窃取 session cookie 的方式,有一些如跨站脚本攻击( XSS )可能会使得 session 固化防护都很脆弱。请访问 OWASP 站点来了解更多防止这种类型攻击的信息。

比较 session-fixation-protection 选项

         session-fixation-protection 属性支持三种不同的选项允许你进行修改:

属性值

描述

none

使得 session 固化攻击失效,不会配置 SessionManagementFilter (除非其它的 <session-management> 属性不是默认值)

migrateSession

当用户经过认证后分配一个新的 session ,它保证原 session 的所有属性移到新 session 中。我们将在后面的章节中讲解,通过基于 bean 的方式如何进行这样的配置。

newSession

当用户认证后,建立一个新的 session ,原(未认证时) session 的属性不会进行移到新 session 中来。

         在大多数场景下,默认行为即 migrateSession 适用于在用户登录后希望保持重要信息(如点击爱好、购物车等)的站点的站点

通过session 的并发控制增强对用户的保护

         紧随 session 固化防护一个很自然的用户安全增强功能就是 session 并发控制。如前面所描述的那样, session 并发控制能够确保一个用户不能同时拥有超过一个固定数量的活跃 session (典型情况是一个)。要确保这个最大值的限制需要涉及到好几个组件的协作以精确跟踪用户 session 活动的变化。

         让我们配置这个功能并了解其如何工作,然后对其进行测试。

配置 session 并发控制

         既然我们要了解 session 并发控制所要涉及的组件,那将其运行环境搭建起来可能会更有感官的了解。首先,我们需要使得 ConcurrentSessionFilter 生效并在 dogstore-security.xml 配置。

 

Xml代码  收藏代码
  1. < http   auto-config = "true"   use-s = "true" >   
  2. <!-- ... -->   
  3.   < session-management >   
  4.     < concurrency-control   max-sessions = "1" />   
  5.   </ session-management >   
  6. </ http >   

 现在,我们需要在 web.xml 描述文件中配置中使得 o.s.s.web.session.HttpSessionEventPublisher 生效,这样 servelt 容器将会通知 Spring Security session 生命周期的事件(通过 HttpSessionEventPublisher )。

 

Xml代码  收藏代码
  1. < listener >   
  2.   < listener-class >    
  3.     org.springframework.web.context.ContextLoaderListener   
  4.   </ listener-class >   
  5. </ listener >   
  6. < listener >   
  7.   < listener-class >    
  8.     org.springframework.security.web.session   
  9.     .HttpSessionEventPublisher   
  10.   </ listener-class >   
  11. </ listener >   
  12. < servlet >   
  13.  < servlet-name > dogstore </ servlet-name >   

          这两个配置完成, session 的并发控制功能也就激活了。让我们看一下它内部是如何工作的,然后我们将会通过几步操作来查看它对用户的 session 的保护功能。

 

 

理解 session 并发控制

         我们在前面提到 session 并发控制试图限制相同的用户以不同的 session 进行访问。基于我们对 session 窃取方式攻击的了解,我们可以发现 session 并发控制能够降低攻击者窃取已登录合法用户 session 的风险。你觉得为什么会这样呢?

         Session 并发控制使用 o.s.s.core.session.SessionRegistry 来维护一个活跃 HTTP session 的列表而认证过的用户与其进行关联。当 session 创建或过期时,注册表中会实时进行更新,基于 HttpSessionEventPublisher 发布的 session 生命周期事件来跟踪每一个认证用户的活动 session 的数量。



 SessionAuthenticationStrategy 的一个扩展类即 o.s.s.web.authentication.session.ConcurrentSessionControlStrategy 提供方法来实现新 session 的跟踪以及 session 并发控制的实际增强功能。每次用户访问这个安全站点时, SessionManagementFilter 将会比照 SessionRegistry 检查这个活跃的 session 。如果用户活跃的 session 不在 SessionRegistry 这个活跃 session 列表中,最近最少被使用的 session 将会立即过期。

         在修改后的 session 并发控制过滤器链中的第二个参与者是 o.s.s.web.session.ConcurrentSessionFilter 。这个过滤器能够辨认出过期的 session (典型情况下, session 会被 servlet 容器或者被 ConcurrentSessionControlStrategy 强制失效掉)并通知用户他的 session 已经失效了。

         既然我们已经了解了 session 并发控制是如何工作的,那对我们来说很容易制造一个它使用的场景。

测试 session 并发控制

         如同验证 session 固化攻击那样,我们需要访问两个 web 浏览器。按一下的步骤:

1.       IE 中,以 guest 用户登录;

2.       接下来,在 Firefox 中,以相同的用户( guest )登录;

3.       最后,返回到 IE 中,做任何的动作都可以。你会发现有一个信息提示你的 session 已经过期了。

将会显示以下的信息:



 尽管不很友好,但是它能够表明 session 已经被软件强制失效了。如果你是一个攻击者,现在你可能会很灰心。但是,如果你是一个合法的用户,你可能会很迷惑,因为这显然不是一个友好的方式来表明 JBCP Pets 一直关注着你的安全。

session 并发控制对 Spring Security 的新用户来说是很难掌握的概念。很多用户在还没有完全理解其怎样运行和能带来什么好处时就尝试实现它。如果你想使用这个强大的功能,并且它不像你预想的那样工作,请确保你的配置全部正确并回顾一下本节讲的原理——希望它能帮助你理解什么出错了。】

         在这样的事件发生时,我们应该将用户重定向到登录页,并提供一个信息来说明发生了什么错误。

配置 session 失效时的重定向地址

         幸运的是,有一种很容易的方法使用户在 session 并发控制后重定向到友好的页面(一般来说,是登录页),即设置 expired-url 属性为一个你应用中合法的页面。

 

Xml代码  收藏代码
  1. < http   auto-config = "true"   use-s = "true" >   
  2. <!-- ... -->   
  3.   < session-management >   
  4.     < concurrency-control   max-sessions = "1"   expired-url "/login.do?error=expired" />   
  5.   </ session-management >   
  6. </ http >   

 这样在我们的应用中,就会将用户重定向到登录 form ,并且我们可以修改这个页面来展现用户友好的信息来表明发现了多个活跃的 session ,从而需要重新登录。当然,这个 URL 是完全随意的,并根据你应用的需求来进行相应的调整。

Session 并发控制的其它好处

         Session 并发控制的另一个好处是存在 SessionRegistry 跟踪活跃的 session (过期 session 是可选的)。这意味着我们能够得到系统中运行时的用户活动信息(至少是认证过的用户)。

         即使你不想使用 session 并发控制,你可以可以这样做。只需将 max-sessions 的值设置为 -1 ,这样 session 跟踪会保持可用,但没有最大 session 个数的限制。

         让我们看两个使用此功能的两种简单方式。

显示活动的用户

         你可能会在线论坛上见到显示系统中当前活跃用户的数量。借助于使用 session 注册跟踪(通过 session 并发控制),很容易实现在应用中的每个页面对此进行展现。

         让我们在 BaseController 中添加一个简单的方法以及 bean 自动织入。 @Autowired

Java代码  收藏代码
  1. SessionRegistry sessionRegistry;  
  2. odelAttribute("numUsers" )  
  3. public   int  getNumberOfUsers() {  
  4.   return  sessionRegistry.getAllPrincipals().size();  
  5. }  

 我们可以看到这暴露了一个能够在 Spring MVC JSP 页面中能够使用的属性,所以我们添加一个页脚 footer.jsp JBCP Pets 站点中并使用这个属性。

 

Html代码  收藏代码
  1. < div   id = "footer" >   
  2.   ${numUsers} user(s) are logged in!  
  3. </ div >   
  4. </ body >   
  5. </ html >   

 如果你重新启动应用并登录,能够在每个页面的底部看到活动用户的数量。



 很简单,但是它阐述了作为 Spring Security 一部分所提供的 session 跟踪的好处——尤其是你能够直接使用这个内置的功能。

         我们能够借助于 SessionRegistry 进行更高级的收据收集,这对于管理员来说很有用——现在让我们看一下。

显示所有用户的信息

         SessionRegistry 跟踪所有活跃用户 session 的信息。如果你想增强站点的管理,我们可以可以很容易地在一个页面中列出所有的用户活跃用户以及他们在站点中使用的名字。

         让我们为 AccountController 添加一个新的方法(尽管这样的功能一般会添加在在管理区域,但是此时我们可以假设 JBCP Pets 并不是一个真正的站点),这个方法将会查找 SessionRegistry 中的信息并收集当前 session 的信息。

 

Java代码  收藏代码
  1. @RequestMapping ( "/account/listActiveUsers.do" )  
  2. public   void  listActiveUsers(Model model) {  
  3.   Map<Object,Date> lastActivityDates = new  HashMap<Object, Date>();  
  4.   for (Object principal: sessionRegistry.getAllPrincipals()) {  
  5.   // a principal may have multiple active sessions   
  6.   for (SessionInformation session : sessionRegistry.getAllSessions(principal,  false ))  
  7.     {  
  8. // no last activity stored   
  9.     if (lastActivityDates.get(principal) ==  null ) {  
  10.       lastActivityDates.put(principal, session.getLastRequest());  
  11.     } else  {  
  12.     // check to see if this session is newer than the last stored   
  13.       Date prevLastRequest = lastActivityDates.get(principal);  
  14.       if (session.getLastRequest().after(prevLastRequest)) {  
  15.           // update if so   
  16.           lastActivityDates.put(principal, session.getLastRequest());  
  17.         }  
  18.       }  
  19.     }  
  20.   }  
  21.   model.addAttribute("activeUsers" , lastActivityDates);  
  22. }  

 这个方法使用了 SessionRegistry 的两个 API

l  getAllPrincipals :返回拥有活跃 session Principal 对象(典型情况下为 UserDetails 对象)所组成的 List

l  getAllSessions(principal, includeExpired) :得到指定 Principal SessionInformation 组成的 List ,包含了每个 session 的信息。也能够包含过期的 session

了解了 SessionRegistry API 方法, listActiveUsers 方法的逻辑就很简单了——检索注册表中所有的活跃用户并寻找最近活动的 session Principal 以及最近活跃的时间戳信息插入到一个 Map 中用来在 UI 中展现。

UI 页面通过使用 JSTL 结构变得很简单了。在 WEB-INF/views/account 目录下,创建 listActiveUsers.jsp ,内容如下(简便起见,我们省略了头部和尾部信息):

 

Xml代码  收藏代码
  1. < %@ taglib  prefix = "c"   uri = "http://java./jsp/jstl/core"  % >   
  2. < h1 > Active Users </ h1 >   
  3. < ul >   
  4.   < c:forEach   items = "${activeUsers}"   var = "uinfo" >   
  5.     < li > < strong > ${uinfo.key.username} </ strong >    
  6.     / Last Active: < strong > ${uinfo.value} </ strong > </ li >   
  7.   </ c:forEach >   
  8. </ ul >   

 最后,当我们导航到 http://localhost:8080/JBCPPets/account/listActiveUsers.do ,页面基本如下:



 通过这些例子,你已经了解到了 SessionRegistry 的威力。我们甚至可以扩展 SessionRegistry 来跟踪用户活动的附加信息,如最后访问的页面、最后的行为等——这对基于 Spring Security 构建管理界面来说是很有用的。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多