2023年11月25日发(作者:)

C#基础知识之理解CookieSession机制

会话(Session)跟踪是Web程序中常⽤的技术,⽤来跟踪⽤户的整个会话。常⽤的会话跟踪技术是CookieSessionCookie通过在客户端记录信息确定⽤户⾝

份,Session通过在服务器端记录信息确定⽤户⾝份。本章将系统地讲述CookieSession机制,并⽐较说明什么时候不能⽤Cookie,什么时候不能⽤Session

⼀、Cookie机制

Cookie技术是客户端的解决⽅案,Cookie就是由服务器发给客户端的特殊信息,⽽这些信息以⽂本⽂件的⽅式存放在客户端,然后客户端每次向服务器发送请求的时候都

会带上这些特殊的信息。让我们说得更具体⼀些:当⽤户使⽤浏览器访问⼀个⽀持Cookie的⽹站的时候,⽤户会提供包括⽤户名在内的个⼈信息并且提交⾄服务器;接着,

服务器在向客户端回传相应的超⽂本的同时也会发回这些个⼈信息,当然这些信息并不是存放在HTTP响应体(Response Body)中的,⽽是存放于HTTP响应头

Response Header);当客户端浏览器接收到来⾃服务器的响应之后,浏览器会将这些信息存放在⼀个统⼀的位置,对于Windows操作系统⽽⾔,我们可以从: [系统

]:Documents and Settings[⽤户名]Cookies⽬录中找到存储的Cookie;⾃此,客户端再向服务器发送请求的时候,都会把相应的Cookie再次发回⾄服务器。⽽这

次,Cookie信息则存放在HTTP请求头(Request Header)了。有了Cookie这样的技术实现,服务器在接收到来⾃客户端浏览器的请求之后,就能够通过分析存放于请求

头的Cookie得到客户端特有的信息,从⽽动态⽣成与该客户端相对应的内容。通常,我们可以从很多⽹站的登录界⾯中看到请记住我这样的选项,如果你勾选了它之后再

登录,那么在下⼀次访问该⽹站的时候就不需要进⾏重复⽽繁琐的登录动作了,⽽这个功能就是通过Cookie实现的。

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

如,⽤户A在超市购买的任何商品都应该放在A的购物车内,不论是⽤户A什么时间购买的,这都是属于同⼀个会话的,不能放⼊⽤户B或⽤户C的购物车内,这不属于同⼀

个会话。

Web应⽤程序是使⽤HTTP⽆状态协议传输数据的。⼀旦数据交换完毕,客户端与服务器端的连接就会关闭,再次交换数据需要建⽴新的连接。这就意味着服务器⽆法从

连接上跟踪会话。即⽤户A购买了⼀件商品放⼊购物车内,当再次购买商品时服务器已经⽆法判断该购买⾏为是属于⽤户A的会话还是⽤户B的会话了。要跟踪该会话,必须

引⼊⼀种机制。Cookie就是这样的⼀种机制。它可以弥补HTTP协议⽆状态的不⾜。在Session出现之前,基本上所有的⽹站都采⽤Cookie来跟踪会话。如果你把Cookies

成为http协议的⼀个扩展的话,理解起来就容易的多了,其实本质上cookies就是http的⼀个扩展。有两个http头部是专门负责设置以及发送cookie,它们分别是Set-Cookie

以及Cookie。当服务器返回给客户端⼀个http响应信息时,其中如果包含Set-Cookie这个头部时,意思就是指⽰客户端建⽴⼀个cookie,并且在后续的http请求中⾃动发送

这个cookie到服务器端,直到这个cookie过期。如果cookie的⽣存时间是整个会话期间的话,那么浏览器会将cookie保存在内存中,浏览器关闭时就会⾃动清除这个

cookie。另外⼀种情况就是保存在客户端的硬盘中,浏览器关闭的话,该cookie也不会被清除,下次打开浏览器访问对应⽹站时,这个cookie就会⾃动再次发送到服务器

端。

1、⼀个cookie的设置以及发送过程分为以下四步:

1)客户端发送⼀个http请求到服务器端

2)服务器端发送⼀个http响应到客户端,其中包含Set-Cookie头部

3)客户端发送⼀个http请求到服务器端,其中包含Cookie头部

4)服务器端发送⼀个http响应到客户端

这个通讯过程也可以⽤以下下⽰意图来描述:

在客户端的第⼆次请求中包含的Cookie头部中,提供给了服务器端可以⽤来唯⼀标识客户端⾝份的信息。这时,服务器端也就可以判断客户端是否启⽤了cookies。尽管,

⽤户可能在和应⽤程序交互的过程中突然禁⽤cookies的使⽤,但是,这个情况基本是不太可能发⽣的,所以可以不加以考虑,这在实践中也被证明是对的。

除了cookies,客户端还可以将发送给服务器的数据包含在请求的url中,⽐如请求的参数或者请求的路径中。 我们来看⼀个常规的http get 请求例⼦:

GET /?foo=bar HTTP/1.1 Host:

另外⼀种客户端传递数据到服务器端的⽅式是将数据包含在http请求的内容区域内。 这种⽅式需要请求的类型是POST的,看下⾯⼀个例⼦:

POST / HTTP/1.1 Host: Content-Type: application/x-www-form-urlencoded Content-Length: 7 foo=bar

在⼀个请求中,可以同时包含这两种形式的数据:

POST /?myget=foo HTTP/1.1 Host: tent-Type: application/x-www-form-urlencoded Content-Length: 11 mypost=bar

这两种传递数据的⽅式,⽐起⽤cookies来传递数据更稳定,因为cookie可能被禁⽤,但是以GET以及POST⽅式传递数据时,不存在这种情况。我们可以将PHPSESSID

含在http请求的url中,就像下⾯的例⼦⼀样:

GET /?PHPSESSID=12345 HTTP/1.1 Host:

2、什么是Cookie

Cookie意为甜饼,是由W3C组织提出,最早由Netscape社区发展的⼀种机制。⽬前Cookie已经成为标准,所有的主流浏览器如IENetscapeFirefoxOpera等都⽀持

Cookie。由于HTTP是⼀种⽆状态的协议,服务器单从⽹络连接上⽆从知道客户⾝份。怎么办呢?就给客户端们颁发⼀个通⾏证吧,每⼈⼀个,⽆论谁访问都必须携带⾃⼰

通⾏证。这样服务器就能从通⾏证上确认客户⾝份了。这就是Cookie的⼯作原理。Cookie实际上是⼀⼩段的⽂本信息。客户端请求服务器,如果服务器需要记录该⽤户状

态,就使⽤response向客户端浏览器颁发⼀个Cookie。客户端浏览器会把Cookie保存起来。当浏览器再请求该⽹站时,浏览器把请求的⽹址连同该Cookie⼀同提交给服务

器。服务器检查该Cookie,以此来辨认⽤户状态。服务器还可以根据需要修改Cookie的内容。

查看某个⽹站颁发的Cookie很简单。在浏览器地址栏输⼊javascript:alert (document. cookie)就可以了(需要有⽹才能查看)。JavaScript脚本会弹出⼀个对话框显⽰本⽹

站颁发的所有Cookie的内容,如图所⽰。

上图中弹出的对话框中显⽰的为Baidu⽹站的Cookie。其中第⼀⾏BAIDUID记录的就是笔者的⾝份helloweenvsfei,只是Baidu使⽤特殊的⽅法将Cookie信息加密了。

注意事项:

Cookie功能需要浏览器的⽀持。如果浏览器不⽀持Cookie(如⼤部分⼿机中的浏览器)或者把Cookie禁⽤了,Cookie功能就会失效。不同的浏览器采⽤不同的⽅式保存

CookieIE浏览器会在“C:Documents and Settings你的⽤户名Cookies”⽂件夹下以⽂本⽂件形式保存,⼀个⽂本⽂件保存⼀个Cookie

3、记录⽤户访问次数

Java中把Cookie封装成了类。每个Cookie都是该Cookie类的对象。服务器通过操作Cookie类对象对客户端Cookie进⾏操作。通过

kie()获取客户端提交的所有Cookie(以Cookie[]数组形式返回),通过kie(Cookie cookie)向客户端设置CookieCookie对象使⽤key-

value属性对的形式保存⽤户状态,⼀个Cookie对象保存⼀个属性对,⼀个request或者response同时使⽤多个Cookie。因为Cookie类位于包.*下⾯,所以

JSP中不需要import该类。

4Cookie的不可跨域名性

很多⽹站都会使⽤Cookie。例如,Google会向客户端颁发CookieBaidu也会向客户端颁发Cookie。那浏览器访问Google会不会也携带上Baidu颁发的Cookie呢?或者

Google能不能修改Baidu颁发的Cookie呢?答案是否定的。Cookie具有不可跨域名性。根据Cookie规范,浏览器访问Google只会携带GoogleCookie,⽽不会携带Baidu

CookieGoogle也只能操作GoogleCookie,⽽不能操作BaiduCookieCookie在客户端是由浏览器来管理的。浏览器能够保证Google只会操作GoogleCookie

不会操作BaiduCookie,从⽽保证⽤户的隐私安全。浏览器判断⼀个⽹站是否能操作另⼀个⽹站Cookie的依据是域名。GoogleBaidu的域名不⼀样,因此Google不能操

BaiduCookie

需要注意的是,虽然⽹站与⽹站同属于Google,但是域名不⼀样,⼆者同样不能互相操作彼此的Cookie。注意:⽤户登录⽹站

之后会发现访问时登录信息仍然有效,⽽普通的Cookie是做不到的。这是因为Google做了特殊处理。本章后⾯也会对Cookie做类似的

处理。

5Unicode编码:保存中⽂

中⽂与英⽂字符不同,中⽂属于Unicode字符,在内存中占4个字符,⽽英⽂属于ASCII字符,内存中只占2个字节。Cookie中使⽤Unicode字符时需要对Unicode字符进⾏编

码,否则会乱码。

提⽰:Cookie中保存中⽂只能编码。⼀般使⽤UTF-8编码即可。不推荐使⽤GBK等中⽂编码,因为浏览器不⼀定⽀持,⽽且JavaScript也不⽀持GBK编码。

6BASE64编码:保存⼆进制图⽚

Cookie不仅可以使⽤ASCII字符与Unicode字符,还可以使⽤⼆进制数据。例如在Cookie中使⽤数字证书,提供安全度。使⽤⼆进制数据时也需要进⾏编码。注意:本程序

仅⽤于展⽰Cookie中可以存储⼆进制内容,并不实⽤。由于浏览器每次请求服务器都会携带Cookie,因此Cookie内容不宜过多,否则影响速度。Cookie的内容应该少⽽

精。

7、设置Cookie的所有属性

除了namevalue之外,Cookie还具有其他⼏个常⽤的属性。每个属性对应⼀个getter⽅法与⼀个setter⽅法。Cookie类的所有属性如下所⽰。

1String name:该Cookie的名称。Cookie⼀旦创建,名称便不可更改。

2Object value:该Cookie的值。如果值为Unicode字符,需要为字符编码。如果值为⼆进制数据,则需要使⽤BASE64编码。

3 int maxAge:该Cookie失效的时间,单位秒。如果为正数,则该Cookie>maxAge秒之后失效。如果为负数,该Cookie为临时Cookie,关闭浏览器即失效,浏览器

也不会以任何形式保存该Cookie。如果为0,表⽰删除该Cookie。默认为–1

4boolean secure:该Cookie是否仅被使⽤安全协议传输。安全协议。安全协议有HTTPSSSL等,在⽹络>上传输数据之前先将数据加密。默认为false

5String path:该Cookie的使⽤路径。如果设置为“/sessionWeb/”,则只有contextPath“/sessionWeb”的程序可以访问该Cookie。如果设置为“/”,则本域名下

contextPath都可以访问该Cookie。注意最后⼀个字符必须为“/”

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

7String comment:该Cookie的⽤处说明。浏览器显⽰Cookie信息的时候显⽰该说明。

8int version:该Cookie使>⽤的版本号。0表⽰遵循NetscapeCookie规范,1表⽰遵循W3CRFC 2109规范。

8Cookie的有效期

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

1)如果maxAge属性为正数,则表⽰该Cookie会在maxAge秒之后⾃动失效。浏览器会将maxAge为正数的Cookie持久化,即写到对应的Cookie⽂件中。⽆论客户关闭了

浏览器还是电脑,只要还在maxAge秒之前,登录⽹站时该Cookie仍然有效。下⾯代码中的Cookie信息将永远有效。

Cookie cookie = new Cookie("username","helloweenvsfei");

Age(_VALUE); // 设置⽣命周期为MAX_VALUE

kie(cookie); // 输出到客户端

View Code

2)如果maxAge为负数,则表⽰该Cookie仅在本浏览器窗⼝以及本窗⼝打开的⼦窗⼝内有效,关闭窗⼝后该Cookie即失效。maxAge为负数的Cookie,为临时性

Cookie,不会被持久化,不会被写到Cookie⽂件中,Cookie信息保存在浏览器内存中,因此关闭浏览器该Cookie就消失了。Cookie默认的maxAge值为–1

3)如果maxAge0,则表⽰删除该CookieCookie机制没有提供删除Cookie的⽅法,因此通过设置该Cookie即时失效实现删除Cookie的效果。失效的Cookie会被浏览

器从Cookie⽂件或者内存中删除:

Cookie cookie = new Cookie("username","helloweenvsfei");

Age(0); // 设置⽣命周期为0,不能为负数

kie(cookie); // 必须执⾏这⼀句

View Code

注意事项:

response对象提供的Cookie操作⽅法只有⼀个添加操作add(Cookie cookie)。要想修改Cookie只能使⽤⼀个同名的Cookie来覆盖原来的Cookie,达到修改的⽬的。删除时

只需要把maxAge修改为0即可。

从客户端读取Cookie时,包括maxAge在内的其他属性都是不可读的,也不会被提交。浏览器提交Cookie时只会提交namevalue属性。maxAge属性只被浏览器⽤来判断

Cookie是否过期。

9Cookie的修改、删除

Cookie并不提供修改、删除操作。如果要修改某个Cookie,只需要新建⼀个同名的Cookie,添加到response中覆盖原来的Cookie。如果要删除某个Cookie,只需要新建⼀

个同名的Cookie,并将maxAge设置为0,并添加到response中覆盖原来的Cookie。注意是0⽽不是负数。负数代表其他的意义。读者可以通过上例的程序进⾏验证,设置

不同的属性。

注意:修改、删除Cookie时,新建的CookievaluemaxAge之外的所有属性,例如namepathdomain等,都要与原Cookie完全⼀样。否则,浏览器将视为两个不同

Cookie不予覆盖,导致修改、删除失败。

10Cookie的域名

Cookie是不可跨域名的。域名颁发的Cookie不会被提交到域名去。这是由Cookie的隐私安全机制决定的。隐私安全机制能够禁⽌⽹站⾮

法获取其他⽹站的Cookie。正常情况下,同⼀个⼀级域名下的两个⼆级域名如也不能交互使⽤Cookie,因为⼆者的

域名并不严格相同。如果想所有名下的⼆级域名都可以使⽤该Cookie,需要设置Cookiedomain参数,例如:

Cookie cookie = new Cookie("time","20080808"); // 新建Cookie

ain("."); // 设置域名

h("/"); // 设置路径

Age(_VALUE); // 设置有效期

kie(cookie); // 输出到客户端

View Code

读者可以修改本机C:WINDOWSsystem32driversetc下的hosts⽂件来配置多个临时域名,然后使⽤程序来设置跨域名Cookie验证domain属性。

注意:domain参数必须以点(".")开始。另外,name相同但domain不同的两个Cookie是两个不同的Cookie。如果想要两个域名完全不同的⽹站共有Cookie,可以⽣成两个

Cookiedomain属性分别为两个域名,输出到客户端。

11Cookie的路径

domain属性决定运⾏访问Cookie的域名,⽽path属性决定允许访问Cookie的路径(ContextPath)。例如,如果只允许/sessionWeb/下的程序使⽤Cookie,可以这么写:

Cookie cookie = new Cookie("time","20080808"); // 新建Cookie

h("/session/"); // 设置路径

kie(cookie); // 输出到客户端

View Code

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

注意:页⾯只能获取它属于的PathCookie。例如/session/test/不能获取到路径为/session/abc/Cookie。使⽤时⼀定要注意。

1domain表⽰的是cookie所在的域,默认为请求的地址,如⽹址为/test/,那么domain默认为。⽽跨域访问,如域A

,域B,那么在域A⽣产⼀个令域A和域B都能访问的cookie就要将该cookiedomain设置为.;如果要在域A⽣产⼀个令域A不能访问⽽域B

能访问的cookie就要将该cookiedomain设置为

2path表⽰cookie所在的⽬录,默认为/,就是根⽬录。在同⼀个服务器上有⽬录如下:/test/,/test/cd/,/test/dd/,现设⼀个cookie1path/test/cookie2path

/test/cd/,那么test下的所有页⾯都可以访问到cookie1,⽽/test//test/dd/的⼦页⾯不能访问cookie2。这是因为cookie能让其path路径下的页⾯访问。

3)浏览器会将domainpath都相同的cookie保存在⼀个⽂件⾥,cookie间⽤*隔开。

12Cookie的安全属性

HTTP协议不仅是⽆状态的,⽽且是不安全的。使⽤HTTP协议的数据不经过任何加密就直接在⽹络上传播,有被截获的可能。使⽤HTTP协议传输很机密的内容是⼀种隐

患。如果不希望CookieHTTP等⾮安全协议中传输,可以设置Cookiesecure属性为true。浏览器只会在HTTPSSSL等安全协议中传输此类Cookie。下⾯的代码设置

secure属性为true

Cookie cookie = new Cookie("time", "20080808"); // 新建Cookie

ure(true); // 设置安全属性

kie(cookie); // 输出到客户端

View Code

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

13JavaScript操作Cookie

Cookie是保存在浏览器端的,因此浏览器具有操作Cookie的先决条件。浏览器可以使⽤脚本程序如JavaScript或者VBScript等操作Cookie。这⾥以JavaScript为例介绍常⽤

Cookie操作。例如下⾯的代码会输出本页⾯所有的Cookie

由于JavaScript能够任意地读写Cookie,有些好事者便想使⽤JavaScript程序去窥探⽤户在其他⽹站的Cookie。不过这是徒劳的,W3C组织早就意识到JavaScriptCookie

的读写所带来的安全隐患并加以防备了,W3C标准的浏览器会阻⽌JavaScript读写任何不属于⾃⼰⽹站的Cookie。换句话说,A⽹站的JavaScript程序读写B⽹站的Cookie

不会有任何结果。

案例:永久登录

如果⽤户是在⾃⼰家的电脑上上⽹,登录时就可以记住他的登录信息,下次访问时不需要再次登录,直接访问即可。实现⽅法是把登录信息如账号、密码等保存在Cookie

中,并控制Cookie的有效期,下次访问时再验证Cookie中的登录信息即可。

保存登录信息有多种⽅案。

1)最直接的是把⽤户名与密码都保持到Cookie中,下次访问时检查Cookie中的⽤户名与密码,与数据库⽐较。这是⼀种⽐较危险的选择,⼀般不把密码等重要信息保存

Cookie中。

2)还有⼀种⽅案是把密码加密后保存到Cookie中,下次访问时解密并与数据库⽐较。这种⽅案略微安全⼀些。

3)如果不希望保存密码,还可以把登录的时间戳保存到Cookie与数据库中,到时只验证⽤户名与登录时间戳就可以了。这⼏种⽅案验证账号时都要查询数据库。

4)本例将采⽤另⼀种⽅案,只在登录时查询⼀次数据库,以后访问验证登录信息时不再查询数据库。实现⽅式是把账号按照⼀定的规则加密后,连同账号⼀块保存到

Cookie中。下次访问时只需要判断账号的加密规则是否正确即可。本例把账号保存到名为accountCookie中,把账号连同密钥⽤MD1算法加密后保存到名为ssidCookie

中。验证时验证Cookie中的账号与密钥加密后是否与Cookie中的ssid相等。相关代码如下:

<%@ page language="java" pageEncoding="UTF-8" isErrorPage="false" %>

<%! // JSP⽅法

private static final String KEY =":cookie@"; // 密钥

public final static String calcMD1(String ss) { // 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 = es(); // 获取字节

MessageDigestmdTemp = tance("MD1"); // 获取MD1

(strTemp); // 更新数据

byte[] md =(); // 加密

int j =; // 加密后的长度

char str[] = new char[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 new String(str); // 加密后字符串

} catch (Exception e){return null; }

}

%>

<%

racterEncoding("UTF-8"); // 设置request编码

racterEncoding("UTF-8"); // 设置response编码

String action =ameter("action"); // 获取action参数

if("login".equals(action)) { // 如果为login动作

String account =ameter("account"); // 获取account参数

String password =ameter("password"); // 获取password参数

int timeout = new Integer(ameter("timeout")); // 获取timeout参数

String ssid =calcMD1(account + KEY); // 把账号、密钥使⽤MD1加密后保存

Cookie accountCookie = new Cookie("account", account); // 新建Cookie

Age(timeout); // 设置有效期

Cookie ssidCookie =new Cookie("ssid", ssid); // 新建Cookie

Age(timeout); // 设置有效期

kie(accountCookie); // 输出到客户端

kie(ssidCookie); // 输出到客户端

// 重新请求本页⾯,参数中带有时间戳,禁⽌浏览器缓存页⾯内容

direct(uestURI() + "?" + tTimeMillis());

return;

} else if("logout".equals(action)) { // 如果为logout动作

CookieaccountCookie = new Cookie("account", ""); // 新建Cookie,内容为空

Age(0); // 设置有效期为0,删除

Cookie ssidCookie =new Cookie("ssid", ""); // 新建Cookie,内容为空

Age(0); // 设置有效期为0,删除

kie(accountCookie); // 输出到客户端

kie(ssidCookie); // 输出到客户端

// 重新请求本页⾯,参数中带有时间戳,禁⽌浏览器缓存页⾯内容

direct(uestURI() + "?" + tTimeMillis());

return;

}

boolean login = false; // 是否登录

String account = null; // 账号

String ssid = null; // SSID标识

if(kies() !=null) { // 如果Cookie不为空

for(Cookie cookie : kies()) { // 遍历Cookie

if(e().equals("account")) // 如果Cookie名为 account

account = ue(); // 保存account内容

if(e().equals("ssid")) // 如果为SSID

ssid = ue(); // 保存SSID内容

}

}

if(account != null && ssid !=null) { // 如果accountSSID都不为空

login = (calcMD1(account + KEY)); // 如果加密规则正确, 则视为已经登录

}

%>

"-//W3C//DTD HTML 4.01Transitional//EN">

<%= login ? "欢迎您回来" : "请先登录"%>

<% if(login){%>

欢迎您, ${ }.   

"${tURI }?action=logout">

注销

<% } else { %>

"${ tURI }?action=login" method="post">

账号: "text"name="account" style="width:

200px; ">

密码: "password" name="password">
有效期: "radio" name="timeout" value="-1"

checked> 关闭浏览器即失效
"radio"

name="timeout" value="<%= 30 *24 * 60 * 60 %>"> 30

内有效
"radio" name="timeout" value=

"<%= _VALUE %>"> 永久有效

"submit"value=" " class=

"button">

<% } %>

View Code

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

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

法,该机制就是安全的。

⼆、Session机制

除了使⽤CookieWeb应⽤程序中还经常使⽤Session来记录客户端状态。Session是服务器端使⽤的⼀种记录客户端状态的机制,使⽤上⽐Cookie简单⼀些,相应的也增

加了服务器的存储压⼒。

Session技术则是服务端的解决⽅案,它是通过服务器来保持状态的。由于Session这个词汇包含的语义很多,因此需要在这⾥明确⼀下 Session的含义。⾸先,我们通常都

会把Session翻译成会话,因此我们可以把客户端浏览器与服务器之间⼀系列交互的动作称为⼀个 Session。从这个语义出发,我们会提到Session持续的时间,会提到在

Session过程中进⾏了什么操作等等;其次,Session指的是服务器端为客户端所开辟的存储空间,在其中保存的信息就是⽤于保持状态。从这个语义出发,我们则会提到往

Session中存放什么内容,如何根据键值从 Session中获取匹配的内容等。要使⽤Session,第⼀步当然是创建Session了。那么Session在何时创建呢?当然还是在服务器端

程序运⾏的过程中创建的,不同语⾔实现的应⽤程序有不同创建Session的⽅法,⽽在Java中是通过调⽤HttpServletRequestgetSession⽅法(使⽤true作为参数)创建

的。在创建了Session的同时,服务器会为该Session⽣成唯⼀的Session id,⽽这个Session id在随后的请求中会被⽤来重新获得已经创建的Session;在Session被创建之

后,就可以调⽤Session相关的⽅法往Session中增加内容了,⽽这些内容只会保存在服务器中,发到客户端的只有Session id;当客户端再次发送请求的时候,会将这个

Session id带上,服务器接受到请求之后就会依据Session id找到相应的Session,从⽽再次使⽤之。正式这样⼀个过程,⽤户的状态也就得以保持了。

1、什么是Session

Session是另⼀种记录客户状态的机制,不同的是Cookie保存在客户端浏览器中,⽽Session保存在服务器上。客户端浏览器访问服务器的时候,服务器把客户端信息以某

种形式记录在服务器上。这就是Session。客户端浏览器再次访问时只需要从该Session中查找该客户的状态就可以了。如果说Cookie机制是通过检查客户⾝上的通⾏证

确定客户⾝份的话,那么Session机制就是通过检查服务器上的客户明细表来确认客户⾝份。Session相当于程序在服务器上建⽴的⼀份客户档案,客户来访的时候只需要

查询客户档案表就可以了。

2、实现⽤户登录

Session对应的类为ssion类。每个来访者对应⼀个Session对象,所有该客户的状态信息都保存在这个Session对象⾥。Session对象是在客户端第

⼀次请求服务器的时候创建的。Session也是⼀种key-value的属性对,通过getAttribute(Stringkey)setAttribute(String keyObjectvalue)⽅法读写客户状态信息。Servlet

⾥通过sion()⽅法获取该客户的Session,例如:

HttpSession session = sion(); // 获取Session对象

ribute("loginTime", new Date()); // 设置Session中的属性

out.println("登录时间为:" +(Date)ribute("loginTime")); // 获取

View Code

3Session属性

request还可以使⽤getSession(boolean create)来获取Session。区别是如果该客户的Session不存在,sion()⽅法会返回null,⽽getSession(true)会先创建

Session再将Session返回。Servlet中必须使⽤request来编程式获取HttpSession对象,⽽JSP中内置了Session隐藏对象,可以直接使⽤。如果使⽤声明了<%@page

session="false" %>,则Session隐藏对象不可⽤。下⾯的例⼦使⽤Session记录客户账号信息。

<%@ page language="java" pageEncoding="UTF-8"%>

""/>

"DateFormat"/>

"rmat"/>

""/>

<%!

DateFormat dateFormat = newSimpleDateFormat("yyyy-MM-dd"); // ⽇期格式化器

%>

<%

racterEncoding("UTF-8"); // 设置request编码

Person[] persons =

{

// 基础数据,保存三个⼈的信息

new Person("Liu Jinghua","password1", 34,

("1982-01-01")),

new Person("Hello Kitty","hellokitty", 23,

("1984-02-21")),

new Person("Garfield", "garfield_pass",23,

("1994-09-12"))

};

String message = ""; // 要显⽰的消息

if(hod().equals("POST"))

{

// 如果是POST登录

for(Person person :persons)

{

// 遍历基础数据,验证账号、密码

// 如果⽤户名正确且密码正确

if(e().equalsIgnoreCase(ameter("username"))&&sword().equals(ameter("password")))

{

// 登录成功,设置将⽤户的信息以及登录时间保存到Session

ribute("person", person); // 保存登录的Person

ribute("loginTime", new Date()); // 保存登录的时间

direct(textPath() + "/");

return;

}

}

message = "⽤户名密码不匹配,登录失败。"; // 登录失败

}

%>

"-//W3C//DTD HTML 4.01Transitional//EN">

// ... HTML代码为⼀个FORM表单,代码略,请看随书光盘

View Code

登录界⾯验证⽤户登录信息,如果登录正确,就把⽤户信息以及登录时间保存进Session,然后转到欢迎页⾯中从Session中获取信息,并将⽤

户资料显⽰出来。

<%@ page language="java" pageEncoding="UTF-8"%>

""/>

"DateFormat"/>

"rmat"/>

""/>

<%!

DateFormat dateFormat = newSimpleDateFormat("yyyy-MM-dd"); // ⽇期格式化器

%>

<%

Person person =(Person)ribute("person"); // 获取登录的person

Date loginTime =(Date)ribute("loginTime"); // 获取登录时间

%>

// ... 部分HTML代码略

您的姓名:<%= e()%>
登录时间:<%= loginTime%>
您的年龄:<%= ()%>
您的⽣⽇:<%=(thday()) %>

View Code

程序运⾏效果如图所⽰。

注意:程序中Session中直接保存了Person类对象与Date类对象,使⽤起来要⽐Cookie⽅便。当多个客户端执⾏程序时,服务器会保存多个客户端的Session。获取

Session的时候也不需要声明获取谁的SessionSession机制决定了当前客户只会获取到⾃⼰的Session,⽽不会获取到别⼈的Session。各客户的Session也彼此独⽴,互

不可见。提⽰:Session的使⽤⽐Cookie⽅便,但是过多的Session存储在服务器内存中,会对服务器造成压⼒。

4Session的⽣命周期

Session保存在服务器端。为了获得更⾼的存取速度,服务器⼀般把Session放在内存⾥。每个⽤户都会有⼀个独⽴的Session。如果Session内容过于复杂,当⼤量客户访

问服务器时可能会导致内存溢出。因此,Session⾥的信息应该尽量精简。Session在⽤户第⼀次访问服务器的时候⾃动创建。需要注意只有访问JSPServlet等程序时才会

创建Session,只访问HTMLIMAGE等静态资源并不会创建Session。如果尚未⽣成Session,也可以使⽤sion(true)强制⽣成Session

Session⽣成后,只要⽤户继续访问,服务器就会更新Session的最后访问时间,并维护该Session。⽤户每访问服务器⼀次,⽆论是否读写Session,服务器都认为该⽤户

Session“活跃(active了⼀次。

1Session的有效期

由于会有越来越多的⽤户访问服务器,因此Session也会越来越多。为防⽌内存溢出,服务器会把长时间内没有活跃的Session从内存删除。这个时间就是Session的超时时

间。如果超过了超时时间没访问过服务器,Session就⾃动失效了。Session的超时时间为maxInactiveInterval属性,可以通过对应的getMaxInactiveInterval()获取,通过

setMaxInactiveInterval(longinterval)修改。

Session的超时时间也可以在中修改。另外,通过调⽤Sessioninvalidate()⽅法可以使Session失效。

2Session的常⽤⽅法

Session中包括各种⽅法,使⽤起来要⽐Cookie⽅便得多。Session的常⽤⽅法如下所⽰。

void setAttribute(String attribute, Object value):设置Session属性。value参数可以为任何Java Object。通常为Java Beanvalue信息不宜过⼤

String getAttribute(String attribute):返回Session属性

Enumeration getAttributeNames():返回Session中存在的属性名

void removeAttribute(String attribute):移除Session属性

String getId():返回SessionID。该ID由服务器⾃动创建,不会重复

long getCreationTime():返回Session的创建⽇期。返回类型为long,常被转化为Date类型

Date createTime = new Date( >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失效

TomcatSession的默认超时时间为20分钟。通过setMaxInactiveInterval(int seconds)修改超时时间。可以修改改变Session的默认超时时间。例如修改为60

钟:

60

注意:参数的单位为分钟,⽽setMaxInactiveInterval(int s)单位为秒。

中定义context时采⽤如下定义(单位为秒):

isWARValidated="false" isInvokerEnabled="true"

isWorkDirPersistent="false"/>

3Session对浏览器的要求

虽然Session保存在服务器,对客户端是透明的,它的正常运⾏仍然需要客户端浏览器的⽀持。这是因为Session需要使⽤Cookie作为识别标志。HTTP协议是⽆状态

的,Session不能依据HTTP连接来判断是否为同⼀客户,因此服务器向客户端浏览器发送⼀个名为JSESSIONIDCookie,它的值为该Sessionid(也就是

()的返回值)。Session依据该Cookie来识别是否为同⼀⽤户。该Cookie为服务器⾃动⽣成的,它的maxAge属性⼀般为–1,表⽰仅当前浏览器内有效,

并且各浏览器窗⼝间不共享,关闭浏览器就会失效。因此同⼀机器的两个浏览器窗⼝访问服务器时,会⽣成两个不同的Session。但是由浏览器窗⼝内的链接、脚本等打开

的新窗⼝(也就是说不是双击桌⾯浏览器图标等打开的窗⼝)除外。这类⼦窗⼝会共享⽗窗⼝的Cookie,因此会共享⼀个Session

注意:新开的浏览器窗⼝会⽣成新的Session,但⼦窗⼝除外。⼦窗⼝会共⽤⽗窗⼝的Session。例如,在链接上右击,在弹出的快捷菜单中选择在新窗⼝中打开时,⼦窗

⼝便可以访问⽗窗⼝的Session。如果客户端浏览器将Cookie功能禁⽤,或者不⽀持Cookie怎么办?例如,绝⼤多数的⼿机浏览器都不⽀持CookieJava Web提供了另⼀

种解决⽅案:URL地址重写。

4URL地址重写

URL地址重写是对客户端不⽀持Cookie的解决⽅案。URL地址重写的原理是将该⽤户Sessionid信息重写到URL地址中。服务器能够解析重写后的URL获取Sessionid

这样即使客户端不⽀持Cookie,也可以使⽤Session来记录⽤户状态。HttpServletResponse类提供了encodeURL(Stringurl)实现URL地址重写,例如:

">

Homepage

该⽅法会⾃动判断客户端是否⽀持Cookie。如果客户端⽀持Cookie,会将URL原封不动地输出来。如果客户端不⽀持Cookie,则会将⽤户Sessionid重写到URL中。重写

后的输出可能是这样的:

";jsessionid=0CCD096E7F8D97B0BE608AFDC3E1931E?c=1&wd=Java">Homepage

即在⽂件名的后⾯,在URL参数的前⾯添加了字符串“;jsessionid=XXX”。其中XXXSessionid。分析⼀下可以知道,增添的jsessionid字符串既不会影响请求的⽂件名,

也不会影响提交的地址栏参数。⽤户单击这个链接的时候会把Sessionid通过URL提交到服务器上,服务器通过解析URL地址获得Sessionid。如果是页⾯重定向

Redirection),URL地址重写可以这样写:

<%

if(“administrator”.equals(userName)) {

direct(RedirectURL(“”));

return;

}

%>

效果跟URL(String url)是⼀样的:如果客户端⽀持Cookie,⽣成原URL地址,如果不⽀持Cookie,传回重写后的带有jsessionid字符串的地址。对于WAP

程序,由于⼤部分的⼿机浏览器都不⽀持CookieWAP程序都会采⽤URL地址重写来跟踪⽤户会话。

注意:TOMCAT判断客户端浏览器是否⽀持Cookie的依据是请求中是否含有Cookie。尽管客户端可能会⽀持Cookie,但是由于第⼀次请求时不会携带任何Cookie(因为并

⽆任何Cookie可以携带),URL地址重写后的地址中仍然会带有jsessionid。当第⼆次访问时服务器已经在浏览器中写⼊Cookie了,因此URL地址重写后的地址中就不会带

jsessionid了。由于Cookie可以被⼈为的禁⽌,必须有其他机制以便在Cookie被禁⽌时仍然能够把session id传递回服务器。经常被使⽤的⼀种技术叫做URL重写,就是

session id直接附加在URL路径的后⾯,附加⽅式也有两种:

⼀种是作为URL路径的附加信息,表现形式为

http://...../xxx;jsessionid= ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764

⼀种是作为查询字符串附加在URL后⾯,表现形式为

http://...../xxx?jsessionid=ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764

这两种⽅式对于⽤户来说是没有区别的,只是服务器在解析的时候处理的⽅式不同,采⽤第⼀种⽅式也有利于把session id的信息和正常程序参数区分开来。为了在整个交

互过程中始终保持状态,就必须在每个客户端可能请求的路径后⾯都包含这个session id

另⼀种技术叫做表单隐藏字段。就是服务器会⾃动修改表单,添加⼀个隐藏字段,以便在表单提交时能够把session id传递回服务器。⽐如下⾯的表单:

"testform" action="/xxx">

"text">

在被传递给客户端之前将被改写成:

"testform" action="/xxx">

"hidden" name="jsessionid" value="ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764">

"text">

这种技术现在已较少应⽤。

在谈论session机制的时候,常常听到这样⼀种误解只要关闭浏览器,session就消失了。其实可以想象⼀下会员卡的例⼦,除⾮顾客主动对店家提出销卡,否则店家绝对

不会轻易删除顾客的资料。对session来说也是⼀样的,除⾮程序通知服务器删除⼀个session,否则服务器会⼀直保留,程序⼀般都是在⽤户做log off的时候发个指令去删

session。然⽽浏览器从来不会主动在关闭之前通知服务器它将要关闭,因此服务器根本不会有机会知道浏览器已经关闭,之所以会有这种错觉,是⼤部分session机制都

使⽤会话cookie来保存session id,⽽关闭浏览器后这个 session id就消失了,再次连接服务器时也就⽆法找到原来的session。如果服务器设置的cookie被保存到硬盘上,

或者使⽤某种⼿段改写浏览器发出的HTTP请求头,把原来的session id发送给服务器,则再次打开浏览器仍然能够找到原来的session。恰恰是由于关闭浏览器不会导致

session被删除,迫使服务器为seesion设置了⼀个失效时间,当距离客户端上⼀次使⽤session的时间超过这个失效时间时,服务器就可以认为客户端已经停⽌了活动,才

会把session删除以节省存储空间。

5Session中禁⽌使⽤Cookie

既然WAP上⼤部分的客户浏览器都不⽀持Cookie,索性禁⽌Session使⽤Cookie,统⼀使⽤URL地址重写会更好⼀些。Java Web规范⽀持通过配置的⽅式禁⽤Cookie。下

⾯举例说⼀下怎样通过配置禁⽌使⽤Cookie。打开项⽬sessionWebWebRoot⽬录下的META-INF⽂件夹(跟WEB-INF⽂件夹同级,如果没有则创建),打开

(如果没有则创建),编辑内容如下: /META-INF/

'1.0' encoding='UTF-8'?>

"/sessionWeb"cookies="false">

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

"false">

部署后TOMCAT便不会⾃动⽣成名JSESSIONIDCookieSession也不会以Cookie为识别标志,⽽仅仅以重写后的URL地址为识别标志了

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

以读写其他的Cookie

三、CookieSession的区别

cookie数据存放在客户的浏览器上,session数据放在服务器上;

cookie不是很安全,别⼈可以分析存放在本地的COOKIE并进⾏COOKIE欺骗,考虑到安全应当使⽤session

session会在⼀定时间内保存在服务器上。当访问增多,会⽐较占⽤你服务器的性能。考虑到减轻服务器性能⽅⾯,应当使⽤COOKIE

单个cookie在客户端的限制是3K,就是说⼀个站点在客户端存放的COOKIE不能超过3K

CookieSession的⽅案虽然分别属于客户端和服务端,但是服务端的session的实现对客户端的cookie有依赖关系的,上⾯我讲到服务端执⾏session机制时候会⽣成

sessionid值,这个id值会发送给客户端,客户端每次请求都会把这个id值放到http请求的头部发送给服务端,⽽这个id值在客户端会保存下来,保存的容器就是cookie

因此当我们完全禁掉浏览器的cookie的时候,服务端的session也会不能正常使⽤(注意:有些资料说ASP解决这个问题,当浏览器的cookie被禁掉,服务端的session任然

可以正常使⽤,ASP我没试验过,但是对于⽹络上很多⽤phpjsp编写的⽹站,我发现禁掉cookie,⽹站的session都⽆法正常的访问)。