cookies

https://www.cnblogs.com/humiao-0626/p/11347764.html

 

 

 HTTP cookies,通常又称作"cookies",已经存在了很长时间,但是仍旧没有被予以充分的理解。首要的问题是存在了诸多误区,认为cookies是后门程序或病毒,或压根不知道它是如何工作的。第二个问题是对于cookies缺少一个一致性的接口。尽管存在着这些问题,cookies仍旧在web开发中起着如此重要的作用,以至于如果cookie在没有可替代品出现的情况下消失,我们许多喜欢的Web应用将变得毫无用处。

       cookies的起源

       早期Web开发面临的最大问题之一是如何管理状态。简言之,服务器端没有办法知道两个请求是否来自于同一个浏览器。那时的办法是在请求的页面中插入一个token,并且在下一次请求中将这个token返回(至服务器)。这就需要在form中插入一个包含token的隐藏表单域,或着在URL的qurey字符串中传递该token。这两种办法都强调手工操作并且极易出错。

       Lou Montulli,那时是网景通讯的一个雇员,被认为在1994年将“magic cookies”的概念应用到了web通讯中。他意图解决的是web中的购物车,现在所有购物网站都依赖购物车。他的最早的说明文档提供了一些cookies工作原理的基本信息该文档在RFC2109中被规范化(这是所有浏览器实现cookies的参考依据),并且最终逐步形成了REF2965.Montulli最终也被授予了关于cookies的美国专利。网景浏览器在它的第一个版本中就开始支持cookies,并且当前所有web浏览器都支持cookies。

       cookie是什么?

       坦白的说,一个cookie就是存储在用户主机浏览器中的一小段文本文件。Cookies是纯文本形式,它们不包含任何可执行代码。一个Web页面或服务器告之浏览器来将这些信息存储并且基于一系列规则在之后的每个请求中都将该信息返回至服务器。Web服务器之后可以利用这些信息来标识用户。多数需要登录的站点通常会在你的认证信息通过后来设置一个cookie,之后只要这个cookie存在并且合法,你就可以自由的浏览这个站点的所有部分。再次,cookie只是包含了数据,就其本身而言并不有害。

       创建cookie

       通过HTTP的Set-Cookie消息头,Web服务器可以指定存储一个cookie。Set-Cookie消息的格式如下面的字符串(中括号中的部分都是可选的)

1 Set-Cookie:value [ ;expires=date][ ;domain=domain][ ;path=path][ ;secure]

      消息头的第一部分,value部分,通常是一个name=value格式的字符串。事实上,原始手册指示这是应该使用的格式,但是浏览器对cookie的所有值并不会按此格式校验。实际上,你可以指定一个不包含等号的字符串并且它同样会被存储。然而,通常性的使用方式是以name=value的格式(并且多数的接口只支持该格式)来指定cookie的值。

       当一个cookie存在,并且可选条件允许的话,该cookie的值会在接下来的每个请求中被发送至服务器。cookie的值被存储在名为Cookie的HTTP消息头中,并且只包含了cookie的值,其它的选项全部被去除。例如:        

1 Cookie : value

       通过Set-Cookie指定的选项只是应用于浏览器端,一旦选项被设置后便不会被服务器重新取回。cookie的值与Set-Cookie中指定的值是完全一样的字符串;对于这些值不会有更近一步的解析或转码操作。如果在指定的请求中有多个cookies,那么它们会被分号和空格分开,例如:

1 Cookie:value1 ; value2 ; name1=value1

 

      服务器端框架通常会提供解析cookies的功能,并且通过编程方式获取cookies的值。

      cookie编码(cookie encoding)

      对于cookie的值进行编码一直都存在一些困惑。通常的观点是cookie的值必须被URL编码,但是这其实是一个谬误,尽管可以对cookie的值进行URL编码。原始的文档中指示仅有三种类型的字符必须进行编码:分号,逗号,和空格。规范中提到可以利用URL编码,但是并不是必须。RFC没有提及任何的编码。然而,几乎所有的实现方式都对cookie的值进行了一些列的URL编码。对于name=value的格式,name和value通常都单独进行编码并且不对等号“=”进行编码操作。

      有效期选项(The expires  option)

      紧跟cookie值后面的每个选项都以分号和空格分割,并且每个选项都指定cookie何时应该被发送到服务器。第一个选项是expires,其指定了cookie何时不会再被发送到服务器端的,因此该cookie可能会被浏览器删掉。该选项所对应的值是一个格式为Wdy,DD-Mon--YYYY HH:MM:SS GMT的值,例如:

1 Set-Cookie:name=Nicholas;expires=Sat, 02 May 2009 23:38:25 GMT

      在没有expires选项时,cookie的寿命仅限于单一的会话中。浏览器的关闭意味这一次会话的结束,所以会话cookie只存在于浏览器保持打开的状态之下。这就是为什么当你登录到一个web应用时经常看到一个checkbox,询问你是否选择存储你的登录信息:如果你选择是的话,那么一个expires选项会被附加到登录的cookie中。如果expires选项设置了一个过去的时间点,那么这个cookie会被立即删除。

       domain选项(The domain option)

      下一个选项是domain,指示cookie将要发送到哪个域或那些域中。默认情况下,domain会被设置为创建该cookie的页面所在的域名。例如本站中的cookie的domain属性的默认值为www.nczonline.com。domain选项被用来扩展cookie值所要发送域的数量。例如:

1 Set-Cookie:name=Nicholas;domain=nczonline.net

       想象诸如Yahoo这样的大型网站都会有许多以name.yahoo.com(例如:my.yahoo.com,finance.yahoo.com,等等)为格式的站点。单独的一个cookie可以简单的通过将其domain选项设置为yahoo.com而发送到所有这些站点中。浏览器会对domain的值与请求所要发送至的域名,做一个尾部比较(即从字符串的尾部开始比较),并且在匹配后发送一个Cookie消息头。

      domain设置的值必须是发送Set-Cookie消息头的域名。例如,我无法向google.com发送一个cookie,因为这个产生安全问题。不合法的domain选项只要简单的忽略即可。

        Path选项(The path option)

       另一个控制何时发送Cookie消息头的方式是指定path选项。与domain选项相同的是,path指明了在发Cookie消息头之前必须在请求资源中存在一个URL路径。这个比较是通过将path属性值与请求的URL从头开始逐字符串比较完成的。如果字符匹配,则发送Cookie消息头,例如:

1 Set-Cookie:name=Nicholas;path=/blog

 

       在这个例子中,path选项值会与/blog,/blogrool等等相匹配;任何以/blog开头的选项都是合法的。要注意的是只有在domain选项核实完毕之后才会对path属性进行比较。path属性的默认值是发送Set-Cookie消息头所对应的URL中的path部分。

        secure选项(The  secure  option)

      最后一个选项是secure。不像其它选项,该选项只是一个标记并且没有其它的值。一个secure cookie只有当请求是通过SSL和HTTPS创建时,才会发送到服务器端。这种cookie的内容意指具有很高的价值并且可能潜在的被破解以纯文本形式传输。例如

1 Set-Cookie:name=Nicholas;secure

      现实中,机密且敏感的信息绝不应该在cookies中存储或传输,因为cookies的整个机制都是原本不安全的。默认情况下,在HTTPS链接上传输的cookies都会被自动添加上secure选项。

        cookie的维护和生命周期(cookie maintenance and lifecycle)

       任意数量的选项都可以在单一的cookie中指定,并且这些选项可以以任何顺序存在,例如

1 Set-Cookie:name=Nicholas; domain=nczonline.net; path=/blog

 

       这个cooke有四个标识符:cookie的name,domain,path,secure标记。要想在将来改变这个cookie的值,需要发送另一个具有相同cookie name,domain,path的Set-Cookie消息头。例如:

1 Set-Cooke:name=Greg; domain=nczonline.net; path=/blog

 

       这将以一个新的值来覆盖原来cookie的值。然而,仅仅只是改变这些选项的某一个也会创建一个完全不同的cookie,例如:

1 Set-Cookie:name=Nicholas; domain=nczonline.net; path=/

        在返回这个消息头后,会存在两个同时拥有“name”的不同的cookie。如果你访问在www.nczonline.net/blog下的一个页面,以下的消息头将被包含进来:

1 Cookie:name=Greg;name=Nicholas

       在这个消息头中存在了两个名为“name”的cookie,path值越详细则cookie越靠前。domain-path越详细则cookie字符串越靠前。假设我在ww.nczonline.net/blog下并且发送了另一个cookie,其设置如下:

1 Set-Cookie:name=Mike

       那么返回的消息头现在则变为:

1 Cookie:name=Mike;name=Greg;name=Nicholas

       由于包含“Mike”的cookie使用了域名(www.nczonline.net)作为其domain值并且以全路径(/blog)作为其path值,则它较其它两个cookie更加详细。

    使用失效日期(using expiration dates)

       当cookie创建时包含了失效日期,这个失效日期则关联了以name-domain-path-secure为标识的cookie。要改变一个cookie的失效日期,你必须指定同样的组合。当改变一个cookie的值时,你不必每次都设置失效日期,因为它不是cookie标识信息的组成部分。例如:

1 Set-Cookie:name=Mike;expires=Sat,03 May 2025 17:44:22 GMT

 

        现在已经设置了cookie的失效日期,所以下次我想要改变cookie的值时,我只需要使用它的名字:

1 Set-Cookie:name=Matt

 

       在cookie上的失效日期并没有改变,因为cookie的标识符是相同的。实际上,只有你手工的改变cookie的失效日期,否则其失效日期不会改变。这意味着在同一个会话中,一个会话cookie可以变成一个持久化cookie(一个可以在多个会话中存在的),反之则不可。为了要将一个持久化cookie变为一个会话cookie,你必须删除这个持久化cookie,这只要设置它的失效日期为过去某个时间之后再创建一个同名的会话cookie就可以实现。

       需要记得的是失效日期是以浏览器运行的电脑上的系统时间为基准进行核实的。没有任何办法来来验证这个系统时间是否和服务器的时间同步,所以当服务器时间和浏览器所处系统时间存在差异时这样的设置会出现错误。

       cookie自动删除(automatic cookie removal)

       cookie会被浏览器自动删除,通常存在以下几种原因:

  • 会话cooke(Session cookie)在会话结束时(浏览器关闭)会被删除
  • 持久化cookie(Persistent cookie)在到达失效日期时会被删除
  • 如果浏览器中的cookie限制到达,那么cookies会被删除以为新建cookies创建空间。详见我的另外一篇关于cookies restrictions的博客

       对于任何这些自动删除来说,Cookie管理显得十分重要,因为这些删除都是无意识的。

       Cookie限制条件(Cookie restrictions)

       在cookies上存在了诸多限制条件,来阻止cookie滥用并保护浏览器和服务器免受一些负面影响。有两种cookies的限制条件:cookies的属性和cookies的总大小。原始的规范中限定每个域名下不超过20个cookies,早期的浏览器都遵循该规范,并且在IE7中有个更近一步的提升。在微软的一次更新中,他们在IE7中增加cookies的限制到50个,与此同时Opera限定cookies个数为30.Safari和Chrome对与每个域名下的cookies个数没有限制。

       发向服务器的所有cookies的最大数量(空间)仍旧维持原始规范中所指出的:4KB。所有超出该限制的cookies都会被截掉并且不会发送至服务器。

        Subcookies

       鉴于cookie的数量限制,开发者提出的subcookies的观点来增加cookies的存储量。Subcookies是一些存储在一个cookie的value中的一些name-value对,并且通常与以下格式类似:

1 name=a=b&c=d&e=f&g=h

      这种方式允许在单个cookie中保存多个name-value对,而不会超过浏览器cookie的数量限制。通过这种方式创建cookies的负面影响是,需要自定义解析方式来提取这些值,相比较而言cookies的格式会更为简单。服务器端框架已开始支持subcookies的存储。我编写的YUI Cookie utility,支持在javascript中读/写subcookies

        Javascript中的cookie(cookie In Javascript)

       通过Javascript中的document.cookie属性,你可以创建,维护和删除cookies。当要创建cookies时该属性等同于Set-Cookie消息头,而在读取cookie时则等同于Cookie消息头。在创建一个cookie时,你需要使用和Set-Cookie期望格式相同的字符串:

1 document.cookie="name=Nicholas;domain=nczonline.net;path=/";

       设置document.cookie属性的值并不会删除存储在页面中的所有cookies。它只简单的创建或修改字符串中指定的cookies。下次发送一个请求到服务器时,这些cookies(通过document.cookie设置的)会和其它通过Set-Cookie消息头设置的cookies一样发送至服务器。所有这些cookies并没有什么明确的不同之处。

       要使用Javascript提取cookie的值时,只要从document.cookie中读取即可。返回的字符串与Cookie消息头中的字符串格式相同,所以多个cookies会被分号和字符串分割。例如:

1 name1=Greg; name2=Nicholas

        鉴于此,你需要手工解析这个cookie字符串来提取真实的cookie数据。当前已有许多描述利用Javascript来解析cookie的资料,包括我的书,Professional Javascript,所以在这我就不再说明。通常利用已存在的Javascript库操作cookie会更简单,如YUI Cookie utility 来在Javascript中处理cookies而不要手工重新创建这些算法。

        通过访问document.cookie返回的cookies遵循发向服务器的cookies一样的访问规则。要通过Javascript访问cookies,该页面和cookies必须在相同的域中,有相同的path,有相同的安全级别。

       注意:一旦cookies通过Javascript设置后遍不能提取它的选项,所以你将不会知道domain,path,expiration日期或secure标记。

        HTTP-Only cookies

       微软的IE6 SP1在cookies中引入了一个新的选项:HTTP-only cookies.HTTP-Only背后的意思是告之浏览器该cookie绝不应该通过Javascript的document.cookie属性访问。设计该特征意在提供一个安全措施来帮助阻止通过Javascript发起的跨站脚本攻击(XSS)窃取cookie的行为(我会在另一篇博客中讨论安全问题,本篇如此已足够)。今天Firefox2.0.0.5+,Opera9.5+,Chrome都支持HTTP-Only cookies。3.2版本的Safari仍不支持。

       要创建一个HTTP-Only cookie,只要向你的cookie中添加一个HTTP-Only标记即可:

1 Set-Cookie: name=Nicholas; HttpOnly

 

       一旦设定这个标记,通过documen.coookie则不能再访问该cookie。IE同时更近一步并且不允许通过XMLHttpRequest的getAllResponseHeaders()或getResponseHeader()方法访问cookie,然而其它浏览器则允许此行为。Firefox在3.0.6中修复了该漏洞,然而仍旧有许多浏览器漏洞存在,complete browser support list列出了这些。

      你不能通过JavaScript设置HTTP-only cookies,因为你不能再通过JavaScript读取这些cookies,这是情理之中的事情。

      总结(conclusion)

      为了高效的利用cookies,仍旧有许多要了解和弄明白的东西。对于一项创建于十多年前但仍旧如最初实现的那样被使用至今的技术来说,这是件多不可思议的事。本篇只是提供了一些每个人都应该知道的关于浏览器cookies的基本指导,但无论如何,也不是一个完整的参考。对于今天的web来说Cookies仍旧起着非常重要的作用,并且不恰当的管理cookies会导致各种安全性的问题,从最糟糕的用户体验到安全漏洞。我希望这篇手册能够激起一些关于cookies的不可思议的亮点。

     写在后面的:

     该文章是2009年的,到现在可能已经算一篇比较老的文章,但是文章中关于cookies的讲解十分详细,最近在学习cookies时,读到该文章,觉得值得大家一读,同时也作为自己的参考吧,所以将原文翻译为中文。文中较为详细的讲解了关于cookies的起源,所要解决的问题,以及cookies的本身的属性和每个属性的作用,以及相关浏览器对于cookies的实现情况和延伸情况,正如作者所表达的那样,cookies作为一项十几年前创建却一直沿用至今的技术,值得每个开发者思考并了解和弄明白关于cookies的一些基本信息和原理,译文中尽量保证还原原文本意,但水平有限,一些语句翻译的比较生涩,建议和原文对比阅读,效果可能会比较好一点。

 

 

会话(Session)跟踪是Web程序中常用的技术,用来跟踪用户的整个会话。常用的跟踪技术就是Cookie和Session。

Cookie通过在客户端记录信息确定用户身份,Session通过在服务器记录确定用户身份。

本章将系统的讲述Cookie和Session机制,并比较说明什么时候不能用Cookie,什么时候不能用Session。

2|0Cookie机制:

在程序中,会话跟踪是很重要的事情。理论上,一个用户的所有请求操作都属于同一个会话,而另一个用户的所有请求操作则应属于另一个会话,二者不能混淆。

例如用户A在超市购买的任何商品都应该放在A购物车内,不论用户A是什么时间购买的,这都是属于同一个会话,不能放入用户B或用户C的购物车里面,这不属于同一个会话。

而Web应用程序是使用HTTP协议传输数据的,HTTP协议是无状态的协议。一旦数据交换完毕,客户端与服务端的链接就会关闭,再次交换数据需要建立新的链接。这就意味着服务器无法从链接上面跟踪会话。

即用户A购买了一件商品放入购物车内,当再次购买商品时服务器已经无法判断该购买行为是属于用户A的还是用户B的会话了。要跟踪该会话,必须引入一种机制。

Cookie就是这样的一种机制,它可以弥补HTTP协议无状态的不足。在Session出现之前,基本上所有的网站都是采用Cookie来跟踪会话。

 

1.什么是Cookie

Cookie意为“甜饼”,是由W3C组织提出,醉在有Netscape社区发展的一种机制。目前Cookie已经成为标准,所有的主流浏览器都支持Cookie。

由于HTTP是一种无状态的协议,服务器但从网络连接上无从知道用户的身份。怎么办呢?就给客户发一个通行证吧,每人一个,无论谁访问都必须携带自己的通行证。这样服务器就能从通行证上确认客户的身份了。这就是Cookie的工作原理。

Cookie实际上是一小段的文本信息。客户端请求服务器,如果服务器需要记录该用户状态,就使用response向客户端浏览器颁发一个Cookie。客户端浏览器会把Cookie保存起来。当浏览器再次请求该网站时,浏览器把请求的网址连同该Cookie一同发送给服务器。服务器检查该Cookie,以此来辨别用户状态。服务器还可以根据需要修改Cookie的内容。

 

2.记录用户访问次数

Java中把Cookie封装成了java.servlt.http.Cookie类。每个Cookie都是该Cookie类的对象。服务器通过操作Cookie类对象对客户端Cookie进行操作。通过request.getCookie()获取客户端提交的所有Cookie(以Cookie[]数组形式返回),通过response.addCookie(Cookiecookie)向客户端设置Cookie。

Cookie对象使用key-value属性对的形式保存用户状态,一个Cookie保存一个属性对,一个request或者response同时使用多个Cookie。因为Cookie类位于包java.servlet.http.*下面,所以JSP中不需要import该类

 

3.Cookie的不可跨域名性

很多网站都会使用Cookie。例如,Google会想客户端颁发Cookie,Baidu也会想客户颁发Cookie。那浏览器访问Google会不会带上Baidu的Cookie呢?或者相反,访问Baidu会不会带上Google的Cookie呢?

答案是否定的。Cookie具有不可跨域名性。根据Cookie规范,浏览器访问Google只会携带Google的Cookie,Google也是只能操作Google的Cookie的。

Cookie在客户端是由浏览器来管理的。浏览器能够保证Google只能操作Google的Cookie,不能操作Baidu的,从而保证用户的隐私安全。浏览器判断一个网站是否能操作另一个网站的Cookie靠的是域名,Google和Baidu的域名不一样,因此Google不能操作Baidu的Cookie。

需要注意的是,虽然网站images.google.com与网站www.google.com同属于Google,但是域名不一样,二者同样不能互相操作彼此的Cookie。

注意:用户登录网站www.google.com之后会发现访问images.google.com时登录信息仍然有效,而普通的Cookie是做不到的。这是因为Google做了特殊处理。本章后面也会对Cookie做类似的处理。

 

4.Unicode编码:保存中文

中文与因为字符不同,中文属于Unicode字节,在内存中占4个字符,而英文属于ASCII字符,内存中自占2个字节。Cookie中使用Unicode字符时需要对Unicode字符进行编码,否则会乱码。

提示:Cookie中保存中文只能编码。一般使用UTF-8编码即可。不推荐使用GBK等中文编码,因为浏览器不一定支持,而且JavaScript也不支持GBK编码。

 

5.BASE64编码:保存二进制图片

Cookie不仅可以使用ASCII字符和Unicode字符,还可以使用二进制数据。例如在Cookie中使用数字证书,提供安全度。使用二进制数据时也需要进行编码。

注意:本程序仅用于展示Cookie中可以存储二进制内容,并不实用。由于浏览器每次求情服务器都会携带Cookie,因此Cookie不宜过多,否则影响速度。Cookie的内容应该少而精。

 

6.设置Cookie的所有属性

除了name与value之外,Cookie还具有其他几个常用的属性。每个属性对应一个grtter方法和一个setter方法。

Cookie类的所有属性表:

属  性  名

描    述

String name

该Cookie的名称。Cookie一旦创建,名称便不可更改

Object value

该Cookie的值。如果值为Unicode字符,需要为字符编码。如果值为二进制数据,则需要使用BASE64编码

int maxAge

该Cookie失效的时间,单位秒。如果为正数,则该Cookie在maxAge秒之后失效。如果为负数,该Cookie为临时Cookie,关闭浏览器即失效,浏览器也不会以任何形式保存该Cookie。如果为0,表示删除该Cookie。默认为–1

boolean secure

该Cookie是否仅被使用安全协议传输。安全协议。安全协议有HTTPS,SSL等,在网络上传输数据之前先将数据加密。默认为false

String path

该Cookie的使用路径。如果设置为“/sessionWeb/”,则只有contextPath为“/sessionWeb”的程序可以访问该Cookie。如果设置为“/”,则本域名下contextPath都可以访问该Cookie。注意最后一个字符必须为“/”

String domain

可以访问该Cookie的域名。如果设置为“.google.com”,则所有以“google.com”结尾的域名都可以访问该Cookie。注意第一个字符必须为“.”

String comment

该Cookie的用处说明。浏览器显示Cookie信息的时候显示该说明

int version

该Cookie使用的版本号。0表示遵循Netscape的Cookie规范,1表示遵循W3C的RFC 2109规范

 

 7.Cookie的有效期

Cookie的maxAge决定着Cookie的有效期,单位为秒(Second)。Cookie中用过getMaxAge()方法与setMaxAge(int maxAge)方法来读写maxAge属性。

如果maxAge属性为正数,则表示该Cookie会在maxAge秒之后自动失效。浏览器会将maxAge为正数的Cookie持久化,即写到对应的Cookie文件中。无论客户关闭了浏览器还是电脑,只要还在maxAge秒之前,登陆网站时该Cookie仍然有效。下面代码中的Cookie信息将永远有效。

Cookie cookie = new Cookie("username","helloweenvsfei"); // 新建Cookie cookie.setMaxAge(Integer.MAX_VALUE); // 设置生命周期为MAX_VALUE response.addCookie(cookie); // 输出到客户端

 

如果maxAge为负数,则表示该Cookie仅在本浏览器窗口以及本窗口打开的子窗口内有效,关闭窗口后该Cookie失效。maxAge为负数的Cookie,为临时性Cookie,不会被持久化,不会被写到Cookie文件中。Cookie信息保存在浏 览器内存中,因此关闭浏览器该Cookie就消失了。Cookie默认的maxAge值为–1。

如果maxAge为0,则表示删除该Cookie。Cookie机制没有提供删除Cookie的方法,因此通过设置该Cookie即时失效实现删除Cookie的效果。失效的Cookie会被浏览器从Cookie文件或者内存中删除,

 
例如:

 

Cookie cookie = new Cookie("username","helloweenvsfei"); // 新建Cookie cookie.setMaxAge(0); // 设置生命周期为0,不能为负数 response.addCookie(cookie); // 必须执行这一句

 

response对象提供的Cookie操作方法只有一个添加操作add(Cookie cookie)。

要想修改Cookie只能使用一个同名的Cookie来覆盖原来的Cookie,达到修改的目的。删除时只需要把maxAge修改为0即可。

 

注意:从客户端读取Cookie时,包括maxAge在内的其他属性都是不可读的,也不会被提交。浏览器提交Cookie时只会提交name与value属性。maxAge属性只被浏览器用来判断Cookie是否过期。

 

 8.Cookie的删除和修改

Cookie修改,如果要修改某个Cookie,只需要新建一个同名的Cookie,添加到response中覆盖原来的Cookie。

Cookie删除,只需要新建一个同名的Cookie,并将maxAge设置为0,并添加到response中覆盖原来的Cookie。注意是设置为0不是负数。负数代表关闭窗口就消失。

注意:修改、删除Cookie时,新建的Cookie除value、maxAge之外的所有属性,例如name、path、domain等,都要与原Cookie完全一样。否则,浏览器将视为两个不同的Cookie不予覆盖,导致修改、删除失败。

 

9.Cookie的域名

Cookie是不可跨域名的。域名www.google.com颁发的Cookie不会被提交到域名www.baidu.com去。这是由Cookie的隐私安全机制决定的。隐私安全机制能够禁止网站非法获取其他网站的Cookie。

正常情况下,同一个一级域名下的两个二级域名如www.helloweenvsfei.com和 images.helloweenvsfei.com也不能交互使用Cookie,因为二者的域名并不严格相同。如果想所有gelloweenvsfei.com名下的二级域名都可以使用该Cookie,需要设置Cookie的domain参数,

例如:

 

Cookie cookie = new Cookie("time","20080808"); // 新建Cookie cookie.setDomain(".helloweenvsfei.com"); // 设置域名 cookie.setPath("/"); // 设置路径 cookie.setMaxAge(Integer.MAX_VALUE); // 设置有效期 response.addCookie(cookie); // 输出到客户端

 

读者可以修改本机C:\WINDOWS\system32\drivers\etc下的hosts文件来配置多个临时域名,然后使用setCookie.jsp程序来设置跨域名Cookie验证domain属性。

注意:domain参数必须以点(".")开始。另外,name相同但domain不同的两个Cookie是两个不同的Cookie。如果想要两个域名完全不同的网站共有Cookie,可以生成两个Cookie,domain属性分别为两个域名,输出到客户端。

 

10.Cookie的路径

domain实行决定运行访问Cookie的域名,而path属性决定允许访问Cookie的路径(ContextPath)。

例如如果允许/sessionWeb/下的程序实行Cookie,可以这么写:

Cookie cookie = new Cookie("time","20080808"); // 新建Cookie cookie.setPath("/session/"); // 设置路径 response.addCookie(cookie); // 输出到客户端

设置为“/”时允许所有路径使用Cookie。path属性需要使用符号“/”结尾。name相同但domain相同的两个Cookie也是两个不同的Cookie。

注意:页面只能获取它属于的Path的Cookie。例如/session/test/a.jsp不能获取到路径为/session/abc/的Cookie。使用时一定要注意。

 

11.Cookie的安全属性

HTTP协议是无状态的,而且是不安全的。使用HTTP协议的数据不经过任何加密就直接在网络上传播,有被截获的可能。使用HTTP协议传输很机密的内容是一种隐患。如果不希望Cookie在HTTP等非安全协议中传输,可以设置Cookie的secure实行为true。浏览器只会在HTTPS和SSL等安全协议中传输此类Cookie。

下面的代码设置secure属性为true:

Cookie cookie = new Cookie("time", "20080808"); // 新建Cookie cookie.setSecure(true); // 设置安全属性 response.addCookie(cookie); // 输出到客户端

注意:secure属性并不能对Cookie内容加密,因而不能保证绝对的安全性。如果需要高安全性,需要在程序中对Cookie内容加密、解密,以防泄密。

 

12.JavaScript操作Cookie

Cookie是保存在浏览器端的,因此浏览器具有操作Cookie的先决条件。浏览器可以使用脚本程序如JavaScript或者VBScript等操作Cookie。这里以JavaScript为例介绍常用的Cookie操作。例如下面的代码会输出页面的所有Cookie。

 

<script>document.write(document.cookie);</script>

 

由于JavaScript能够任意的读写Cookie,有些好事者便想使用JavaScript程序去窥探用户在其他网站的Cookie。不顾这是徒劳的,W3C组织早就意识到JavaScript对Cookie的读写所带来的安全隐患并加以防备了,W3C标准的浏览器会阻止JavaScript读写任何不属于自己网站的Coookie。换句话说,A网站的JavaScript程序读写B网站的Cookie不会有任何的结果。

 

13.案例:永久登录

 

如果用户是在自己家的电脑上上网,登录时就可以记住他的登录信息,下次访问时不需要再次登录,直接访问即可。实现方法是把登录信息如账号、密码等保存在Cookie中,并控制Cookie的有效期,下次访问时再验证Cookie中的登录信息即可。

 

保存登录信息有多种方案。最直接的是把用户名与密码都保持到Cookie中,下次访问时检查Cookie中的用户名与密码,与数据库比较。这是一种比较危险的选择,一般不把密码等重要信息保存到Cookie中

 

还有一种方案是把密码加密后保存到Cookie中,下次访问时解密并与数据库比较。这种方案略微安全一些。如果不希望保存密码,还可以把登录的时间戳保存到Cookie与数据库中,到时只验证用户名与登录时间戳就可以了。

 

这几种方案验证账号时都要查询数据库。

 

本例将采用另一种方案,只在登录时查询一次数据库,以后访问验证登录信息时不再查询数据库。实现方式是把账号按照一定的规则加密后,连同账号一块保存到Cookie中。下次访问时只需要判断账号的加密规则是否正确即可。本例把账号保存到名为account的Cookie中,把账号连同密钥用MD1算法加密后保存到名为ssid的Cookie中。验证时验证Cookie中的账号与密钥加密后是否与Cookie中的ssid相等。相关代码如下:代码1.8 loginCookie.jsp

<%@ page language="java"pageEncoding="UTF-8" isErrorPage="false" %> <%! // JSP方法 private static final String KEY =":cookie@helloweenvsfei.com";// 密钥 public final static String calcMD1(Stringss) { // MD1 加密算法 String s = ss==null ?"" : ss; // 若为null返回空 char hexDigits[] = { '0','1', '2', '3', '4', '1', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; // 字典 try { byte[] strTemp =s.getBytes(); // 获取字节 MessageDigestmdTemp = MessageDigest.getInstance("MD1"); // 获取MD1 mdTemp.update(strTemp); // 更新数据 byte[] md =mdTemp.digest(); // 加密 int j =md.length; // 加密后的长度 char str[] = newchar[j * 2]; // 新字符串数组 int k =0; // 计数器k for (int i = 0; i< j; i++) { // 循环输出 byte byte0 =md[i]; str[k++] =hexDigits[byte0 >>> 4 & 0xf]; str[k++] =hexDigits[byte0 & 0xf]; } return newString(str); // 加密后字符串 } catch (Exception e){return null; } } %> <% request.setCharacterEncoding("UTF-8"); // 设置request编码 response.setCharacterEncoding("UTF-8"); // 设置response编码 String action =request.getParameter("action"); // 获取action参数 if("login".equals(action)){ // 如果为login动作 String account =request.getParameter("account");// 获取account参数 String password =request.getParameter("password");// 获取password参数 int timeout = newInteger(request.getParameter("timeout"));// 获取timeout参数 String ssid =calcMD1(account + KEY); // 把账号、密钥使用MD1加密后保存 CookieaccountCookie = new Cookie("account", account);// 新建Cookie accountCookie.setMaxAge(timeout); // 设置有效期 Cookie ssidCookie =new Cookie("ssid", ssid); // 新建Cookie ssidCookie.setMaxAge(timeout); // 设置有效期 response.addCookie(accountCookie); // 输出到客户端 response.addCookie(ssidCookie); // 输出到客户端 // 重新请求本页面,参数中带有时间戳,禁止浏览器缓存页面内容 response.sendRedirect(request.getRequestURI() + "?" + System. currentTimeMillis()); return; } elseif("logout".equals(action)){ // 如果为logout动作
CookieaccountCookie = new Cookie("account", ""); // 新建Cookie,内容为空 accountCookie.setMaxAge(0); // 设置有效期为0,删除 Cookie ssidCookie =new Cookie("ssid", ""); // 新建Cookie,内容为空 ssidCookie.setMaxAge(0); // 设置有效期为0,删除 response.addCookie(accountCookie); // 输出到客户端 response.addCookie(ssidCookie); // 输出到客户端 //重新请求本页面,参数中带有时间戳,禁止浏览器缓存页面内容 response.sendRedirect(request.getRequestURI() + "?" + System. currentTimeMillis()); return; } boolean login = false; // 是否登录 String account = null; // 账号 String ssid = null; // SSID标识 if(request.getCookies() !=null){ // 如果Cookie不为空 for(Cookie cookie :request.getCookies()){ // 遍历Cookie if(cookie.getName().equals("account")) // 如果Cookie名为account account = cookie.getValue(); // 保存account内容 if(cookie.getName().equals("ssid")) // 如果为SSID ssid = cookie.getValue(); // 保存SSID内容 } } if(account != null && ssid !=null){ // 如果account、SSID都不为空 login =ssid.equals(calcMD1(account + KEY));// 如果加密规则正确, 则视为已经登录 } %> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01Transitional//EN"> <legend><%= login ? "欢迎您回来" : "请先登录"%></legend> <% if(login){%> 欢迎您, ${ cookie.account.value }. &nbsp;&nbsp; <a href="${ pageContext.request.requestURI }?action=logout"> 注销</a> <% } else {%> <formaction="${ pageContext.request.requestURI }?action=login" method="post"> <table> <tr><td>账号: </td> <td><input type="text"name="account" style="width: 200px; "></td> </tr> <tr><td>密码: </td> <td><inputtype="password" name="password"></td> </tr> <tr> <td>有效期: </td> <td><inputtype="radio" name="timeout" value="-1" checked> 关闭浏览器即失效 <br/> <input type="radio" name="timeout" value="<%= 30 *24 * 60 * 60 %>"> 30天 内有效 <br/><input type="radio" name="timeout" value= "<%= Integer.MAX_VALUE %>"> 永久有效 <br/> </td> </tr> <tr><td></td> <td><input type="submit"value=" 登 录 " class= "button"></td> </tr> </table> </form> <% } %>

登录时可以选择登录信息的有效期:关闭浏览器即失效、30天内有效与永久有效。通过设置Cookie的age属性来实现,注意观察代码。运行效果如图所示。

提示:该加密机制中最重要的部分为算法与密钥。由于MD1算法的不可逆性,即使用户知道了账号与加密后的字符串,也不可能解密得到密钥。因此,只要保管好密钥与算法,该机制就是安全的。

3|0 Session机制

除了使用cookie,Web应用程序中还经常使用Session来记录客户端状态。Session是服务器端使用的一种记录客户端状态的机制,使用上比Cookie简单一些,相应的也增加了服务器的存储压力。

 

1.什么是Session

Session是另一种记录客户状态的机制,不同的是Cookie保存在客户端浏览器中,Session保存在服务器上。客户端浏览器访问服务器的时候,服务端把客户端信息以某种形式记录在服务器上,这就是Session。客户端浏览器再次访问时只需要从该Session中查找该客户的状态就可以了。

如果说Cookie机制是通过检查客户身上的"通行证"来确定客户身份的话,那么Session及时就是通过检查服务器上的"客户明细表"来确认身份。Session相当于程序在服务器上建立的一份客户档案,客户来访时只需要检查客户的档案表就可以了。

 

2.实现用户登录

Session对应的类为javax.servlet.http.HttpSession类。每个来访者对应一个Session对象,所有该客户的状态信息都保存在这个Session对象里。Session对象是在客户端第一次请求服务器的时候创建的。 Session也是一种key-value的属性对,通过getAttribute(Stringkey)和setAttribute(String key,Objectvalue)方法读写客户状态信息。Servlet里通过request.getSession()方法获取该客户的 Session,

 

HttpSession session = request.getSession(); // 获取Session对象 session.setAttribute("loginTime", new Date()); // 设置Session中的属性 out.println("登录时间为:" +(Date)session.getAttribute("loginTime")); // 获取Session属性

当多个客户端执行程序时,服务器会保存多个客户端的Session。获取Session的时候也不需要声明获取谁的Session。Session机制决定了当前客户只会获取到自己的Session,而不会获取到别人的Session。各客户的Session也彼此独立,互不可见

 

提示:Session的使用比Cookie方便,但是过多的Session存储在服务器内存中,会对服务器造成压力。

3.Session的生命周期

Session保存在服务器端。为了获得更高的存取速度,服务器一般吧Session放在内存里。每个用户都会有一个独立的Session。如果Session内用过于复杂,当大量客户访问服务器时可能导致内存溢出。因此,Session里的信息尽量精简。

Session在用户第一次访问服务器的时候自动创建。需要注意只有访问JSP、Serviet等程序时才会创建Session,只访问HTML、IMAGE等静态资源并不会创建Session。如果尚未生成Session,也可能使用request.getSession(true)强制生成Session。

Session生成后,只要用户继续访问,服务器就会更新Session的最后访问时间,并维护该Session。用户每访问一次,无论是否读写Session,服务器都认为该用户的Session活跃(active)了一次。

 

4.Session的有效期

由于有越来越多的用户访问服务器,因此Session也会越来越多。为防止内存溢出,服务器会把长时间没有活跃的Session从内存中删除。这个时间就是Session的超时时间。如果超过了超时时间没有访问过服务器,Session就自动失效。

Session的超时时间为maxUnactiveInterval属性,可以通过getMacInactiveInterval()获取,通过setMaxInacyiveInterval(longinterval)修改。

Session的超时时间也可以在web.xml中修改。另外,通过调用Session的invalidate()方法可以是Session失效。

 

5.Session常用的方法

Session中包括各种办法,使用起来要比Cookie方便的多。Session的常用方法如下:

方  法  名

描    述

void setAttribute(String attribute, Object value)

设置Session属性。value参数可以为任何Java Object。通常为Java Bean。value信息不宜过大

String getAttribute(String attribute)

返回Session属性

Enumeration getAttributeNames()

返回Session中存在的属性名

void removeAttribute(String attribute)

移除Session属性

String getId()

返回Session的ID。该ID由服务器自动创建,不会重复

long getCreationTime()

返回Session的创建日期。返回类型为long,常被转化为Date类型,例如:Date createTime = new Date(session.get CreationTime())

long getLastAccessedTime()

返回Session的最后活跃时间。返回类型为long

int getMaxInactiveInterval()

返回Session的超时时间。单位为秒。超过该时间没有访问,服务器认为该Session失效

void setMaxInactiveInterval(int second)

设置Session的超时时间。单位为秒

void putValue(String attribute, Object value)

不推荐的方法。已经被setAttribute(String attribute, Object Value)替代

Object getValue(String attribute)

不被推荐的方法。已经被getAttribute(String attr)替代

boolean isNew()

返回该Session是否是新创建的

void invalidate()

使该Session失效

 

Tomcat中Session的默认超时时间为20分钟。通过setMaxInactiveInterval(int seconds)修改超时时间。可以修改web.xml改变Session的默认超时时间。例如修改为60分钟:

<session-config> <session-timeout>60</session-timeout> <!-- 单位:分钟 --> </session-config>

 

注意:<session-timeout>参数的单位为分钟,而setMaxInactiveInterval(int s)单位为秒。

 

6.Session对浏览器的要求

虽然Session保存在服务器,对客户端是透明的,他的正常运行任然需要客户端浏览器的支持。这是因为Session需要使用Cookie作为识别标志。HTTP协议是无状态的,Session不能依据HTTP链接来判断是否为同一客户,因此服务器先客户端浏览器发送一个名为JSESSIONID的Cookie,他的值为该Session的id(也就是HTTPSession.getId()的返回值)。Session依据Cookie来识别是否为同一用户。

该Cookie为服务器自动生成的,它的maxAge属性一般为-1,表示仅当前浏览器内有效,并且个浏览器窗口间不共享,关闭浏览器就会失效。

因此同一机器的两个浏览器窗口访问服务器时,会生成两个不同的Session、但是由浏览器窗口内的链接、脚本等打开的新窗口(也就是说不是双击=桌面浏览器图标打开的窗口)除外。这类子窗口会共享父窗口的Cookie,因此会共享一个Session。

 

如果客户端浏览器将Cookie功能禁用,或者不支持Cookie怎么办?例如,绝大多数的手机浏览器都不支持Cookie。Java Web提供了另一种解决方案:URL地址重写。

 

 

 

 

7.URL地址重写

URl地址重写是对客户端不支持Cookie的解决方案。URL地址重写的原理是将该用户Session的id信息重写到URL地址中。服务器能够顾解析重写后的URL获取Session的id。这样即使客户端不支持Cookie,也可以使用Session来记录用户状态。HttpServletResponse类提供了encodeURL(Stringurl)实现URL地址重写。

例如:

 

<td> <a href="<%=response.encodeURL("index.jsp?c=1&wd=Java") %>"> Homepage</a> </td>

 

即在文件名的后面,在URL参数的前面添加了字符串";jsessionid=xxx"。其中xxx为Session的id。分分析一下可以知道,添加的jsessinid字符串即不会影响请求的文件名,也不会影响提交的地址栏参数。用户单击这个链接的时候会把Session的id通过URL提交到服务器上,服务器听过解析URL地址获得Session的id。

如果页面重定向(Redirection),URL底子重写可以这样写:

<% if(“administrator”.equals(userName)) { response.sendRedirect(response.encodeRedirectURL(“administrator.jsp”)); return; } %>

效果跟response.encodeURL(String url)是一样的:如果客户端支持Cookie,生成原URL地址,如果不支持Cookie,传回重写后的带有jsessionid字符串的地址。

对于WAP程序,由于大部分的手机浏览器都不支持Cookie,WAP程序都会采用URL地址重写来跟踪用户会话。比如用友集团的移动商街等。

 

注意:TOMCAT判断客户端浏览器是否支持Cookie的依据是请求中是否含有Cookie。尽管客户端可能会支持Cookie,但是由于第一次请求时不会携带任何Cookie(因为并无任何Cookie可以携带),URL地址重写后的地址中仍然会带有jsessionid。当第二次访问时服务器已经在浏览器中写入Cookie了,因此URL地址重写后的地址中就不会带有jsessionid了。

 

8.Session中禁止使用Cookie

既然既然WAP上大部分的客户浏览器都不支持Cookie,索性禁止Session使用Cookie,统一使用URL地址重写会更好一些。Java Web规范支持通过配置的方式禁用Cookie。下面举例说一下怎样通过配置禁止使用Cookie。

 

打开项目sessionWeb的WebRoot目录下的META-INF文件夹(跟WEB-INF文件夹同级,如果没有则创建),打开context.xml(如果没有则创建),编辑内容如下:

 

代码1.11 /META-INF/context.xml

 

<?xml version='1.0' encoding='UTF-8'?> <Context path="/sessionWeb"cookies="false"> </Context>

或者修改Tomcat全局的conf/context.xml,修改内容如下:

代码1.12  context.xml

<!-- The contents of this file will be loaded for eachweb application --> <Context cookies="false"> <!-- ... 中间代码略 --> </Context>

部署后TOMCAT便不会自动生成名JSESSIONID的Cookie,Session也不会以Cookie为识别标志,而仅仅以重写后的URL地址为识别标志了。

 

注意:该配置只是禁止Session使用Cookie作为识别标志,并不能阻止其他的Cookie读写。也就是说服务器不会自动维护名为JSESSIONID的Cookie了,但是程序中仍然可以读写其他的Cookie。

posted @ 2020-12-29 15:45  zJanly  阅读(109)  评论(0编辑  收藏  举报