##实现原理## Java的web容器都实现了session机制,实现的逻辑思想都是一致的,可是具体方案可能会存在必定差别,这里我以tomcat容器为例,探讨下session实现的机制。php
下图是tomcat源码里session实现: html
实现包的路径是:org.apache.catalina.session,tomcat对外提供session调用的接口不在这个实现包里,对外接口是在包javax.servlet.http下的HttpSession,而实现包里的StandardSession是tomcat提供的标准实现,固然对外tomcat不但愿用户直接操做StandardSession,而是提供了一个StandardSessionFacade类,tomcat容器里具体操做session的组件是servlet,而servlet操做session是经过StandardSessionFacade进行的,这样就能够防止程序员直接操做StandardSession所带来的安全问题。(StandardSessionFacade使用了设计模式里的Facade(外观)模式,外观模式能让不一样逻辑层的组件进行解耦)。java
实现类里有Manager的类是用来管理session的工具类,它负责建立和销毁session对象,其中ManagerBase是全部session管理工具类的基类,它是一个抽象类,全部具体实现session管理功能的类都要继承这个类,该类有一个受保护的方法,该方法就是建立sessionId值的方法(tomcat的session的id值生成的机制是一个随机数加时间加上jvm的id值,jvm的id值会根据服务器的硬件信息计算得来,所以不一样jvm的id值都是惟一的),StandardManager类是tomcat容器里默认的session管理实现类,它会将session的信息存储到web容器所在服务器的内存里。PersistentManagerBase也是继承ManagerBase类,它是全部持久化存储session信息的基类,PersistentManager继承了PersistentManagerBase,可是这个类只是多了一个静态变量和一个getName方法,目前看来意义不大,对于持久化存储session,tomcat还提供了StoreBase的抽象类,它是全部持久化存储session的基类,另外tomcat还给出了文件存储FileStore和数据存储JDBCStore两个实现。nginx
##安全运用## ###运用问题### 由上面所描述的session实现机制,咱们会发现,为了弥补http协议的无状态的特色,服务端会占用必定的内存和cpu用来存储和处理session计算的开销,这也就是tomcat这个的web容器的并发链接那么低(tomcat官方文档里默认的链接数是200)缘由之一。所以不少java语言编写的网站,在生产环境里web容器以前会加一个静态资源服务器,例如:apache服务器或nginx服务器,静态资源服务器没有解决http无状态问题的功能,所以部署静态资源的服务器也就不会让出内存或cpu计算资源专门去处理像session这样的功能,这些内存和cpu资源能够更有效的处理每一个http请求,所以静态资源服务器的并发链接数更高,因此咱们可让那些没有状态保持要求的请求直接在静态服务器里处理,而要进行状态保持的请求则在java的web容器里进行处理,这样能更好的提高网站的效率。程序员
当下的互联网网站为了提升网站安全性和并发量,服务端的部署的服务器的数量每每是大于或等于两台,多台服务器对外提供的服务是等价的,可是不一样的服务器上面确定会有不一样的web容器,由上面的讲述咱们知道session的实现机制都是web容器里内部机制,这就致使一个web容器里所生成的session的id值是不一样的,所以当一个请求到了A服务器,浏览器获得响应后,客户端存下的是A服务器上所生成的session的id,当在另外一个请求分发到了B服务器,B服务器上的web容器是不能识别这个session的id值,更不会有这个sessionID所对应记录下来的信息,这个时候就须要两个不一样web容器之间进行session的同步。Tomcat容器有一个官方的解决方案就是使用apache+tomcat+mod_jk方案,当一个web容器里session的信息发生变化后,该web容器会向另外一个web容器进行广播,另外一个web收到广播后将session信息同步到本身的容器里,这个过程是十分消耗系统资源,当访问量增长会严重影响到网站的效率和稳定性。web
我如今所作的网站里有一个解决方案,当用户请求网站的时候会先将请求发送给硬件的负载均衡设备,该设备能够截获客户端发送过来的session的id值,而后咱们根据这个id值找到产生这个session的服务器,将请求直接发送给这台服务器。这种解决方案看起来解决了session共享问题,其实结果是将集群系统最终变回了单点系统,若是处理请求的web容器挂掉了,那么用户的相关会话操做也就废掉了。此外,这种作法也干扰了负载均衡服务器的负载均衡的计算,让请求的分发并非公平的。redis
通常大型互联公司的网站都是有一个个独立的频道所组成的,例如咱们经常使用的百度,会有百度搜索,百度音乐,百度百科等等,我相信他们不会把这些不一样频道都给一个开发团队完成,应该每一个频道都是一个独立开发团队,由于每一个频道的应用的都是独立的web应用,那么就存在一个跨站点的session同步的问题,跨站点的登陆可使用单点登陆的(SSO)的解决方案,可是无论什么解决方案,跨站点的session共享任然是逃避不了的问题。算法
由上所述,Session一共有两个问题须要解决: (1)Session的存储应该独立于web容器,也要独立于部署web容器的服务器; (2)如何进行高效的Session同步;数据库
在讲到解决这些问题以前,咱们首先要考虑下session如何存储才是高效,是存在内存、文件仍是数据库了?文件和数据库的存储方式都是将session的数据固化到硬盘上,操做硬盘的方式就是IO,IO操做的效率是远远低于操做内存的数据,所以文件和数据库存储方式是不可取的,因此将session数据存储到内存是最佳的选择。所以最好的解决方案就是使用分布式缓存技术,例如:memcached和redis,将session信息的存储独立出来也是解决session同步问题的方法。apache
Tomcat的Session同步也有使用memcache的解决方案,你们能够参加下面的文章: http://blog.sina.com.cn/s/blog_5376c71901017bqx.html
可是该方案只是解决了同步问题,session机制仍然和web容器紧耦合,咱们须要一个高效、可扩展的解决方案,那么咱们就应该不是简单的把session独立出来存储而是设计一个彻底独立的session机制,它既能给每一个web应用提供session的功能又能够实现session同步,下面是一篇用zookeeper实现的分布式session方案: http://www.open-open.com/lib/view/open1378556537303.html
###安全问题### Http是一种无状态性的协议。这是由于此种协议不要求浏览器在每次请求中标明它本身的身份,而且浏览器以及服务器之间并无保持一个持久性的链接用于多个页面之间的访问。当一个用户访问一个站点的时候,用户的浏览器发送一个http请求到服务器,服务器返回给浏览器一个http响应。其实很简单的一个概念,客户端一个请求,服务器端一个回复,这就是整个基于http协议的通信过程。
由于web应用程序是基于http协议进行通信的,而咱们已经讲过了http是无状态的,这就增长了维护web应用程序状态的难度, 对于开发者来讲,是一个不小的挑战。Cookies是做为http的一个扩展诞生的,其主要用途是弥补http的无状态特性,提供了一种保持客户端与服务器端之间状态的途径,可是因为出于安全性的考虑,有的用户在浏览器中是禁止掉cookie的。这种状况下,状态信息只能经过url中的参数来传递到服务器端,不过这种方式的安全性不好。事实上,按照一般的想法,应该有客户端来代表本身的身份,从而和服务器之间维持一种状态,可是出于安全性方面的考虑,咱们都应该明白一点 – 来自客户端的信息都是不能彻底信任的。
尽管这样,针对维持web应用程序状态的问题,相对来讲,仍是有比较优雅的解决方案的。不过,应该说是没有完美的解决方案的,再好的解决方案也不可能适用全部的状况。这篇文章将介绍一些技术。这些技术能够用来比较稳定地维持应用程序的状态以及抵御一些针对session的攻击,好比会话劫持。而且你能够学习到cookie是怎样工做的,php 的session作了那些事情,以及怎样才能劫持session。
如何才能保持web应用程序的状态以及选择最合适的解决方案呢?在回答这个问题以前,必须得先了解web的底层协议 – Hypertext Transfer Protocol (HTTP)。
当用户访问http://example.com这个域名的时候,浏览器就会自动和服务器创建tcp/ip链接,而后发送http请求到example.com的服务器的80端口。该个请求的语法以下所示:
GET / HTTP/1.1 Host: example.org
以上第一行叫作请求行,第二个参数(一个反斜线在这个例子中)表示所请求资源的路径。反斜线表明了根目录;服务器会转换这个根目录为服务器文件系统中的一个具体目录。
第二行描述的是http头部的语法。在这个例子中的头部是Host, 它标识了浏览器但愿获取资源的域名主机。还有不少其它的请求头部能够包含在http请求中,好比user-Agent头部,在php能够经过$_SERVER['HTTP_USER_AGENT']获取请求中所携带的这个头部信息。
可是遗憾的是,在这个请求例子中,没有任何信息能够惟一标识当前这个发出请求的客户端。有些开发者借助请求中的ip头部来惟一标识发出这次请求的客户端,可是这种方式存在不少问题。由于,有些用户是经过代理来访问的,好比用户A经过代理B链接网站www.example.com, 服务器端获取的ip信息是代理B分配给A的ip地址,若是用户这时断开代理,而后再次链接代理的话,它的代理ip地址又再次改变,也就说一个用户对应了多个ip地址,这种状况下,服务器端根据ip地址来标识用户的话,会认为请求是来自不一样的用户,事实上是同一个用户。 还用另一种状况就是,好比不少用户是在同一个局域网里经过路由链接互联网,而后都访问www.example.com的话,因为这些用户共享同一个外网ip地址,这会致使服务器认为这些用户是同一个用户发出的请求,由于他们是来自同一个ip地址的访问。
保持应用程序状态的第一步就是要知道如何来惟一地标识每一个客户端。由于只有在http中请求中携带的信息才能用来标识客户端,因此在请求中必须包含某种能够用来标识客户端惟一身份的信息。
如今,咱们来看下一个比较常规的针对session的攻击:
用户访问http://www.example.org,而且登陆。 example.org的服务器设置指示客户端设置相关cookie – PHPSESSID=12345 攻击者这时访问http://www.example.org/,而且在请求中携带了对应的cookie – PHPSESSID=12345 这样状况下,由于example.orge的服务器经过PHPSESSID来辨认对应的用户的,因此服务器错把攻击者当成了合法的用户。
整个过程的描述,请看下面的示例图:
固然这种攻击的方式,前提条件是攻击者必须经过某种手段固定,劫持或者猜想出某个合法用户的PHPSESSID。虽然这看起来难度很高,可是也不是不可能的事情。
有不少技术能够用来增强Session的安全性,主要思想就是要使验证的过程对于合法用户来讲,越简单越好,而后对于攻击者来讲,步骤要越复杂越好。固然,这彷佛是比较难于平衡的,要根据你应用程序的具体设计来作决策。
最简单的居于HTTP/1.1请求包括请求行以及一些Host的头部:
GET / HTTP/1.1 Host: example.org
若是客户端经过PHPSESSID传递相关的session标识符,能够将PHPSESSID放在cookie头部中进行传递:
GET / HTTP/1.1 Host: example.org Cookie: PHPSESSID=12345
一样地,客户端也能够将session标识符放在请求的url中进行传递。
GET /?PHPSESSID=12345 HTTP/1.1 Host: example.org
固然,session标识符也能够包含在POST数据中,可是这对用户体验有影响,因此这种方式不多采用。
由于来自TCP/IP信息也不必定能够彻底信任的,因此,对于web开发者来讲,利用TCP/IP中的信息来增强安全性也是不太合适的。 不过,攻击者也必须提供一个合法用户的惟一的标识符,才能假扮成合法用户进入系统。所以,看起来惟一可以有效的保护系统的措施,就是尽可能地隐藏session标识符或者使之难于猜想出来。最好就是二者都能实施。
PHP会自动生成一个随机的session ID,基原本说是不可能被猜想出来的,因此这方面的安全仍是有必定保障的。可是,要防止攻击者获取一个合法的session ID是至关困难的,这基本上不是开发者所能控制的。
事实上,许多状况下都有可能致使session ID的泄露。 好比说,若是经过GET数据来传递session ID的话,就有可能暴露这个敏感的身份信息。由于,有的用户可能会将带有session ID的连接缓存,收藏或者发送在邮件内容中。Cookies是一种像相对来讲安全一点的机制,可是用户是能够在客户端中禁止掉cookies的!在一些IE的版本中也有比较严重的安全漏洞,比较有名的就是会泄露cookies给一些有安全隐患的邪恶站点。
所以,做为一个开发者,能够确定session ID是不能被猜想出来的,可是仍是有可能被攻击者使用某些方法获取到。因此,必须采起一些额外的安全措施来防止此类状况在你的应用程序中发生。
实际上,一个标准的HTTP请求中除了Host等必须包含的头部,还包含了一些可选的头部.举一个例子,看下面的一个请求:
GET / HTTP/1.1 Host: example.org Cookie: PHPSESSID=12345 User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en-US; rv:1.8.1.1) Gecko/20061204 Firefox/2.0.0.1 Accept: text/html;q=0.9, /;q=0.1 Accept-Charset: ISO-8859-1, utf-8;q=0.66, *;q=0.66 Accept-Language: en
咱们能够看到,在以上的一个请求例子中包含了四个额外的头部,分别是User-Agent, Accept, Accept-Charset以及Accept-Language。由于这些头部不是必须的,因此彻底依赖他们在你的应用程序中发挥做用是不太明智的。可是,若是一个用户的浏览器确实发送了这些头部到服务器,那么能够确定的是在接下来的同一个用户经过同一个浏览器发送的请求中,必然也会携带这些头部。固然,这其中也会有极少数的特殊状况发生。假如以上例子是由一个当前的跟服务器创建了会话的用户发出的请求,考虑下面的一个请求:
GET / HTTP/1.1 Host: example.org Cookie: PHPSESSID=12345 User-Agent: Mozilla/5.0
由于有相同的session id包含在请求的Cookie头部中,因此相同的php session将会被访问到。可是,请求里的User-Agent头部跟先前的请求中的信息是不一样的,系统是否能够假定这两个请求是同一个用户发出的?
像这种状况下,发现浏览器的头部改变了,可是不能确定这是不是一次来自攻击者的请求的话,比较好的措施就是弹出一个要求输入密码的输入框让用户输入,这样的话,对用户体验的影响不会很大,又能颇有效地防止攻击。
固然,你能够在系统中加入核查User-Agent头部的代码,相似PHP代码:
<?php session_start(); if (md5($_SERVER['HTTP_USER_AGENT']) != $_SESSION['HTTP_USER_AGENT']) { /* 弹出密码输入框 */ exit; } ?>
固然,你先必须在第一次请求时,初始化session的时候,用MD5算法加密user agent信息而且保存在session中,相似PHP代码:
<?php session_start(); $_SESSION['HTTP_USER_AGENT'] = md5($_SERVER['HTTP_USER_AGENT']); ?>
虽然不必定须要用MD5来加密这个User-Agent信息,但使用这种方式之后就不须要再过滤这个$_SERVER['HTTP_USER_AGENT']数据了。否则的话,在使用这个数据之前必需要进行数据过滤,由于任何来自客户端的数据都是不可信任的,必需要注意这一点。
在你检查这个User-Agent客户端头部信息之后,作为一个攻击者必需要完成两步才能劫持一个session:
获取一个合法的session id 包含一个相同的User-Agent头部在伪造的请求中
你可能会说,竟然攻击者能得到有效的session id,那么以他的水平,伪造一个相同的User-Agent不是件难事。不错,可是咱们能够说这至少给他添加了一些麻烦,在必定程度上也增长了session机制的安全性。
你应该也能想到了,既然咱们能够检查User-Agent这个头部来增强安全性,那么不妨再利用其它的一些头部信息,把他们组合起来生成一个加密的token,而且让客户端在后续的请求中携带这个token!这样的话,攻击者基本上不可能猜想出这样一个token是怎么生成出来的。这比如你用信用卡在超市付款,一个你必须有信用卡(比如session id),另外你也必须输入一个支付密码(比如token),这有这二者都符合的状况下,你才能成功进入帐号付款。 看下面一段代码:
<?php session_start(); $token = 'SHIFLETT' . $_SERVER['HTTP_USER_AGENT']; $_SESSION['token'] = md5($token . session_id()); ?>
注意:Accept这个头部不该该被用来生成token,由于有些浏览器会自动改变这个头部,当用户刷新浏览器的时候。
在你的验证机制中加入了这个很是难于猜想出来的token之后,安全性会获得很大的提高。假如这个token经过像session id同样的方式来进行传递,这种状况下,一个攻击者必须完成必要的3步来劫持用户的session:
获取一个合法的session ID 在请求中加入相同的User-Agent头部,用与生成token 在请求中携带被攻击者的token
这里面有个问题。若是session id以及token都是经过GET数据来传递的话,那么对于能获取session ID的攻击者,一样就可以获取到这个token。因此,比较安全靠谱的方式应该是利用两种不一样的数据传递方式来分别传递session id以及token。例如,经过cookie来传递session id,而后经过GET数据来传递token。所以,假如攻击者经过某种手段得到了这个惟一的用户身份标识,也是不太可能同时轻松地获取到这个token,它相对来讲依然是安全的。
还有不少的技术手段能够用来增强你的session机制的安全性。但愿你在大体了解session的内部本质之后,能够设计出适合你的应用系统的验证机制,从而大大的提升系统的安全性。毕竟,你是最熟悉当下你开发的系统的开发者之一,能够根据实际状况来实施一些特有的,额外的安全措施。
以上只是大概地描述了session的工做机制,以及简单地阐述了一些安全措施。但要记住,以上的方法都是可以增强安全性,不是说可以彻底保护你的系统,但愿读者本身再去调研相关内容。在这个调研过程当中,相信你会学到颇有实际使用价值的方案。