首先解释一下什么是会话。在计算机术语中,会话是指一个终端用户与交互系统进行通讯的过程,比如从输入账户密码进入操作系统到退出操作系统就是一个会话过程。会话较多用于网络上,TCP的三次握手就创建了一个会话,TCP关闭连接就是关闭会话。用平述的语言可以解释为:你拔打你女友的电话号码,你女友接听,然后一翻“亲爱的”,直到任何一方挂掉电话,这个过程就是一个会话。你挑逗一只小狗,它跟你互动,也是会话;它不鸟你,那就不形成会话。
二、什么是 HTTP 会话
协议的状态是指下一次传输可以“记住”这次传输信息的能力,HTTP是不会为了下一次连接而维护这次连接所传输的信息的。从传统WEB上看:无状态是指,当浏览器发送请求给服务器的时候,服务器响应,但是同一个浏览器再发送请求给服务器的时候,他会响应,但是他不知道你就是刚才那个浏览器,简单地说,就是服务器不会去记得你,所以是无状态协议。本质是:HTTP1.0是短连接的(这里先忽略HTTP1.1的keep alive吧),请求响应后,断开了TCP连接,下一次连接与上一次无关。为了识别不同的请求是否来自同一客户,引用HTTP会话机制,即:多次HTTP连接间维护用户与同一用户发出的不同请求之间关联的情况称为维护一个会话(session)。通过会话管理对会话进行创建、信息存储、关闭等。
三、 HTTP 会话的实现机制
Cookie与session是各种教材,网上文章所介绍到的与HTTP会话相关的两个内容。这种者较常见的解释是:cookie存在在浏览器,session存储在服务器中。这种解释是最显浅的,很不严谨,但又不能说是错误。先从cookie谈起吧,很久很久以前,为了完成HTTP会话,那些互联网的设计者们想到了一个办法,就是在浏览器中存储用户信息,每次请求都向服务端发送这些信息,这样服务端就知道请求发送者是谁了,就知道应该返回什么信息给客户了。但是问题很快就出现了,张三冒充李四的名字发送请求给服务器,服务器把李四的相关信息发给了张三。为了安全起见,互联网老大哥们又想到了一招识别用户身份的办法,就是把客户信息存储在服务端(session),一切用户的身份由服务器指定。直到目前,session已成功HTTP会话的主流,应该说是绝对控制的地位。
Session是怎样做到会话身份识别的呢?首先,用户端向服务端发送一个请求,服务端接收到请求(这里忽悠无须会话控制的情况)后,初始化会话,生成相应的会话信息,核心是会话ID,把会话ID发送给客户端,客户端接收到这个会话ID,把它存储起来,下一次发送请求的时候,附带着这个会话ID一起发送给服务端,服务端只要根据这个会话ID,就知道是谁了。这个会话ID,就像我们的身份证号码,一直伴随终生。核心:服务端如何生成这个会话ID,客户端怎样存储这个会话ID。
四、如何存储会话 ID ( SESSION ID )
服务端存储会话ID有多种方式,常见的有本地存储,如:普通文本,文本名就是会话ID。对于文件系统,同一目录下,同一文件名只允许唯一一个文件,那么使用会话ID作为文件名是可以做到唯一确定会话的。除了本地文件存储,还可以使用memcache、redis、或者Mysql之类的数据库存储,即使用第三方数据库进行存储。只有一个原则:存储的会话ID必须是唯一的。
客户端收到服务端返回的(或者说服务端下发的)会话ID后,也是像服务端那样使用文件名作为会话ID存储会话信息到文本吗?如果客户端只与同一个服务端(理解为同一个服务端处理程序)进行会话通讯的话,是可行的。但是,HTTP是因万维网而生的,浏览器作为最常见的HTTP客户端,需要访问各种不同的网站,如果采用会话ID作为文件名,以这样的文件存在会话信息的话,会出现这样的情况:N个不同的网站,服务端采用的是相同的会话生成算法,在同一时刻,很可能会生成一样的会话ID,客户端则无法唯一确定这个会话ID到底是与哪个服务端通讯,也就是客户端“不认得”服务端了,会话就无法完成。如何确定服务端身份?那就是使用“域”,不同的域拥有独立的会话。客户端以域相关信息作为文件标识符创建会话文件(客户端存储)对会话信息进行存储,其中域与会话ID结合就能唯一确定服务端,并且确定会话。那么,以“域”信息作为文件名的文件中存储着会话ID等信息。每次请求某个域的服务时,把存储着的会话ID附带到请求中发送到服务端。浏览器是最常见的HTTP客户端,浏览器存储会话信息,是使用COOKIE文件的,里面保存着COOKIE信息,而服务端返回的会话ID也存储在里面。会话ID存储在COOKIE文件中是一般情况下的,而COOKIE信息是作为HTTP头发送给服务端的,也就是说这种情况下,会话ID是附带在请求头中。但是,HTTP请求,除了头信息,还可以有内容体,必须有URL。那么,会话ID同样可以存储在内容体中或URL中,比如在禁用浏览器COOKIE的情况下,也可实现与服务端会话,要么依赖内容体,要么依赖URL,常见的是URL中附带会话ID,这个在PHP等编程语言中较为常见(曾经的历史上常见,但是会涉及安全或者效率等问题,这里不详述)。
粗糙地,可理解为服务端返回给客户端的会话ID是存储在COOKIE文件中的。COOKIE文件是由浏览器管理的,当然在自实现的客户端中,可以通过编程手段实现COOKIE文件管理,即客户端会话的管理。举例:IOS开发者,可以把HTTP返回的信息头存储到沙盒中进行管理。PHP开发客户端时,可以把信息头写到文件中,或第三方服务中,或网络存储中等等。
五、会话管理( SESSION )
会话管理包括:会话创建、会话识别、会话信息操作、会话生命周期、会话关闭。
注意:这一节中的服务端会话都看作是开启的,无特别情况不再交待。
1、 会话创建
客户端发起不带会话ID(SESSION ID)的HTTP请求,服务端认为还没产生会话,即创建会话,生成会话ID并且在服务器中存储相关会话信息,并通知客户端已开启会话。一般情况下,是在返回给客户端的HTTP header中的COOKIE项中附带上会话ID,形式为:会话标记:会话ID。客户端根据返回的信息头,设置本地COOKIE值并存储。
2、 会话识别
会话ID是会话的唯一标识符,一个会话ID只会对应一个会话,就像身份证号码只对应一个人一样。HTTP中,服务端是被动接受请求的,会话识别也是被动的(触发式)。服务端不需要知道发送请求的到底是谁,只需要知道对方发送过来的会话ID,把客户端传过来的会话ID与服务端存储的会话ID进行匹配。找不到这个会话ID,就认为这个会话是不存在的。
举例:服务器有个会话ID是“21412545jladfjljljqwr”,映射的值是“名字:张三,性别:男”。客户端只要请求中的会话ID是“21412545jladfjljljqwr”,就识别到这个会话了,能认为这人是张三,而且是男性。如果客户端请求的会话ID是“qwesadfasdfadsfasdf”,即使客户端附带了信息“名字:张三,性别:男”,服务端都认为不存在此人,不形成会话。就算是李四盗用了张三的会话ID,服务端也会识别这个会话。
可简单理解为:SESSION只根据SESSION ID建立起会话,是不负责安全校验的,只负责让服务端与客户端可以“通话”。
3、 会话信息操作
服务端:会话ID映射信息,ID不变,映射的内容可变
客户端:会话ID映射信息,ID不变,映射的内容可变(即存在在COOKIEk中的内容可变)。
服务端与客户端的会话信息只有会话ID是必须相同的,其它会话信息(即会话ID映射的信息)没有直接关系。
4、 会话生命周期
会话从开始到结束就是会话的生命周期。设定一个时间,这个时间内无通讯就清除会话信息,我们就把这个时间叫做会话超时周期。
习惯地,我们把会话超时周期叫做会话的生命周期,其实这是两个概念。
5、 会话关闭
会话关闭,有2种方式。一种是用户主动清除会话信息,另一种是会话超时。会话超时不是守护任务(或自动任务)周期性检查处理的,而是访问会话信息时,根据会话信息中的“上一次更新时间”到现在的时间差,与会话周期比较,超出周期的,清除会话信息,即会话关闭。
经典例子:会话过程中,突然断网。
六、会话校验与 HTTP 协议幂等性
HTTP幂等性简述:
从定义上看,HTTP方法的幂等性是指一次和多次请求某一个资源应该具有同样的副作用。幂等性属于语义范畴,正如编译器只能帮助检查语法错误一样,HTTP规范也没有办法通过消息格式等语法手段来定义它,这可能是它不太受到重视的原因之一。但实际上,幂等性是分布式系统设计中十分重要的概念,而HTTP的分布式本质也决定了它在HTTP中具有重要地位。
举个例子(摘抄网上):假设有一个从账户取钱的远程API(可以是HTTP的,也可以不是),我们暂时用类函数的方式记为:bool withdraw(account_id, amount)。请求服务端,减小account_id的amount金额,成功返回true;失败金额不变,返回false。
如果服务端成功了,并返回true,但网络中断,客户端收不到信息,客户端认为取钱失败,再次请求,服务端再一次扣费。这里就涉及一个重复请求同一操作的问题了。
要解决这个问题,我们可以把withdraw设计为幂等的。create_ticket的语义是获取一个服务器端生成的唯一的处理号ticket_id,它将用于标识后续的操作。idempotent_withdraw和withdraw的区别在于关联了一个ticket_id,一个ticket_id表示的操作至多只会被处理一次,每次调用都将返回第一次调用时的处理结果。这样,idempotent_withdraw就符合幂等性了,客户端就可以放心地多次调用。
从上面例子可以看到create_cicket的作用是生成ID识别码,后续操作均基于此ID。会话ID本质上也是幂等性的,生成ID后,后续操作均带上ID参数,即建立操作信息与ID的对应关系。上面的例子并不是安全的,只是确保了操作对于同一个人(一次会话过程)是唯一的。同样,会话ID只作为身份唯一的识别,不是安全的保证。
简单会话校验:
一种较简单的会话校验是使用令牌,即请求中除了会话ID,至少还携带了令牌。服务端对令牌校验。令牌由服务端根据某种算法生成,令牌校验也在服务端中处理,客户端只需存储令牌,在请求中携带令牌,令牌生成算法的复杂程度影响令牌校验的安全性。
举例:tokenFunc(param,value=’’) 第一个参数为令牌生成参数,第二个参数为Token值。当第二参数为空时,成生Token,返回string;第二个参数不为空时,检查Token准确性 ,返回bool. 一般不需要解密,只要散列加密即可。PHP代码如下:
function token($param,$value=’’){
if(!is_string($param){
$param = serialize($param);
}
$token = md5($param.’sault’);
if(!empty($value)){
if($value == $token){
return true;
}else{
return false;
}
}else{
return $token;
}
}
生成令牌:$token = token($session_id);
检验令牌:$check = token($session_id,$token);
浏览器默认是开启Cookie的,浏览器发起HTTP请求时,在请求头中带有Cookie信息,只要服务端返回Cookie中包含SessionID,在服务端根据Sessionid即实现HTTP会话,此过程对于前端开发者是透明的(即前端开发可以不关心浏览器是怎样与服务端确定会话的)。
除即时通讯,实时动作网游外,大多APP是使用HTTP协议与服务端通讯的,使用HTTP协议的原因主要是移动网络环境复杂(容易断线),并且HTTP协议穿透性强。原生开发的IOS,安卓等APP,与服务端会话,可不使用COOKIE,只需要在请求中携带会话ID即可,这在上文已描述。原生APP与内嵌浏览器的APP相比:原生实现性能更高,交互效果流畅,用户体验相对较好,但快速跌代比不上内嵌浏览器的APP。手机配置越来越高,内嵌浏览器对HTML5支持也越来越好,在性能要求不是很高的场景,内嵌WEB的性能已可满足,在布局多变,或者元素多变的情况下,可快速修改,而无需用户升级APP,也能获得更好的产品体验。APP内嵌WEB最常见的场景就是电商APP了,登陆、注册、入口等交互效果较多的模块使用原生程序开发,而商品列表、商品展示等等模块可采用内嵌WEB,这样既可满足快速产品跌代的要求,又可满足操作的性能要求。
举例:电商APP入门界面、登陆、注册是使用原生开发的,登陆后跳转到商品列表页(即内嵌WEB),然后下订单。问题来了,如何使得登陆后跳转到WEB后,还是登陆状态(即内嵌WEB与原生程序具有一致的会话 )呢?内嵌WEB是不会去取得原生程序所存储的data的。最简单直接的办法就是:登陆成功,服务器返回会话ID与成功信息,跳转到WEB时,发送的HTTP请求头中包括COOKIE,会话ID存储在COOKIE中,这样之后点击WEB中的链接后向服务端发送的HTTP请求头,就会携带这个COOKIE(会话ID)了。简单地理解:终端原生程序请求服务端,服务端按普通WEB那样返回信息,终端原生程序取得HTTP返回头中的COOKIE信息,保存下来,下一次请求时,携带COOKIE信息即可。在浏览器中,COOKIE的处理由浏览器默认处理,而在原生APP程序中,由开发者写程序去处理而已。