会话跟踪是 Web 程序中常用的技术,用来跟踪用户的整个会话。
常用的会话跟踪技术是 Cookie 和 Session。
Cookie 在客户端记录信息来确定用户身份,Session 在服务器记录信息来确定用户身份。
Cookie
理论上,一个用户的所用请求操作都应该属于同一个会话,另一用户的所有请求操作则应该属于另一会话,二者不能混淆。
Web 应用程序一般使用 HTTP 协议进行数据传输,而 HTTP 协议是无状态的协议,一旦数据交换完成,客户端与服务器的连接就会关闭,若再次请求,就需要建立新的连接,这意味着服务器无法从连接上跟踪会话。
Cookie 机制可以弥补 HTTP 协议无状态的缺点。
什么是 Cookie
Cookie 是由 W3C 组织提出的一种机制,目前已成为标准,几乎所有主流浏览器都支持 Cookie。
由于 HTTP 是一种无状态的协议,服务器单从网络连接上无法知道用户的身份,于是就给客户端颁发一种通行证,每人一个,无论谁访问都必须携带自己的通行证,服务器就可以确认其身份,这就是 Cookie 大致的工作原理。
Cookie 实际上是一小段文本信息。如果客户端请求服务器,服务器需要记录用户状态,就通过 Response 向客户端发送一个 Cookie,客户端会把 Cookie 保存起来。当浏览器(即客户端)再次请求该网站时,浏览器把本次请求连同 Cookie 一同提交给服务器。服务器检查该 Cookie,以此辨认用户信息。
Java 中的 Cookie
Java 把 Cookie 封装成了 javax.servlet.http.Cookie
类,每个 Cookie 对应一个 Cookie 类对象。
服务器通过 Cookie 对象来对客户端的 Cookie 进行操作:
request.getCookie()
可以获取客户端提交的所有 Cookie,以Cookie[]
数组的形式返回request.addCookie(Cookie cookie)
可以向客户端设置 Cookie
Cookie 对象使用 k-v 属性对的形式保存用户状态,一个 Cookie 对象保存一个属性对,一个 Request 或 Response 包含多个 Cookie。
Cookie 的不可跨域名性
Google 会向客户端颁发 Cookie,Baidu 也会向客户端颁发 Cookie,当浏览器访问 Google 是否会携带 Baidu 颁发的 Cookie?或者 Google 是否能修改 Baidu 颁发的 Cookie?
答案是否定的。Cookie 具有不可跨域名性。根据 Cookie 规范,浏览器访问 Google 只会携带 Google 的 Cookie,而不会携带 Baidu 的Cookie。Google 也只能操作自己Cookie,而不能操作 Baidu 的 Cookie。
Cookie 在客户端由浏览器进行管理,而浏览器判断一个网站能否操作另一网站 Cookie 的方式是域名。
值得注意的是,虽然 images.google.com
与 www.google.com
同属于 Google,但二者域名不一样,不能操作彼此的 Cookie。
另一方面,登录 www.google.com
后,访问 images.google.com
登录信息仍然有效,是因为 Google 的 Cookie 经过了特殊处理,后文将会介绍。
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规范。 |
Cookie 的有效期
Cookie 的 maxAge 为 Cookie 的有效期,单位为秒(Second)。
Cookie 中通过 getMaxAge()
方法与 setMaxAge(int maxAge)
方法来读写 maxAge 属性。
若 maxAge 为正数,则 Cookie 经过此时间后自动失效。浏览器会将 maxAge 为正数的 Cookie 持久化到对应的 Cookie 文件中。无论关闭浏览器还是电脑,在 maxAge 有效时间内,该 Cookie 仍然有效。
Cookie cookie = new Cookie("username","rayucan"); // 新建Cookie cookie.setMaxAge(Integer.MAX_VALUE); // 设置生命周期为MAX_VALUE response.addCookie(cookie); // 输出到客户端
若 maxAge 为负数,则 Cookie 仅在本浏览器窗口及其子窗口有效,关闭窗口后 Cookie 即失效。负数 maxAge 的 Cookie 为临时性 Cookie ,不会被持久化到文件中,Cookie 信息保存在浏览器内存中,因此关闭浏览器该 Cookie 就消失了。
Cookie 默认 maxAge 为 -1。
若 maxAge 为 0,则表示该 Cookie 将被删除。Cookie 机制没有提供直接删除 Cookie 的方法,因此通过设置 maxAge 来实现删除 Cookie 的效果。
Cookie cookie = new Cookie("username","rayucan"); // 新建Cookie cookie.setMaxAge(0); // 设置生命周期为0,不能为负数 response.addCookie(cookie); // 必须执行这一句
另外,从客户端读取 Cookie 时,包括 maxAge 在内的其他属性都是不可读的,也不会被提交,浏览器提交 Cookie 时只会提交 name 和 value 属性,maxAge 只被浏览器用来判断 Cookie 是否过期。
Cookie 的修改和删除
Response 对象提供操作 Cookie 的方法只有一个添加操作 add(Cookie cookie)
。
要想修改 Cookie,需要新建一个同名 Cookie 添加到 response 中,覆盖原有的 Cookie。
要想删除 Cookie,需要新建一个同名 Cookie,并将 maxAge 设置为 0 添加到 response 中,覆盖原有的 Cookie。
Cookie 的域名
Cookie 是不可跨域名的。域名 www.google.com
颁发的Cookie不会被提交到域名 www.baidu.com
。这是由Cookie的隐私安全机制决定的。隐私安全机制能够禁止网站非法获取其他网站的Cookie。
正常情况下,同一个一级域名下的两个二级域名如 www.google.com
和 images.google.com
也不能交互使用Cookie,因为二者的域名并不严格相同。如果想所有二级域名都可以使用该 Cookie,需要设置 Cookie 的 domain 参数:
Cookie cookie = new Cookie("time","20080808"); // 新建Cookie
cookie.setDomain(".google.com"); // 设置域名
cookie.setPath("/"); // 设置路径
cookie.setMaxAge(Integer.MAX_VALUE); // 设置有效期
response.addCookie(cookie); // 输出到客户端
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 的安全属性
HTTP 协议不仅是无状态的,而且是不安全的。使用 HTTP 协议的数据不经任何加密就直接在网络上传播,有被截获的可能。使用 HTTP 协议传输私密信息是一种安全隐患。
如果不希望 Cookie 在 HTTP 等非安全协议中传输,可以设置 Cookie 的 secure 属性为 true,此时浏览器只会在 HTTPS 或 SSL 等安全协议中传输此类 Cookie:
Cookie cookie = new Cookie("time", "20080808"); // 新建Cookie
cookie.setSecure(true); // 设置安全属性
response.addCookie(cookie); // 输出到客户端
值得注意的是,secure 属性并不能对 Cookie 内容加密,因而不能保证绝对的安全性。如果需要高安全性,需要在程序中对Cookie内容加密、解密,以防泄密。
永久登录
如果用户是在自己家的电脑上上网,登录时就可以记住他的登录信息,下次访问时不需要再次登录,直接访问即可。
实现方法是把登录信息如账号、密码等保存在 Cookie 中,并控制 Cookie 的有效期,下次访问时再验证 Cookie 中的登录信息即可。
保存登录信息有多种方案。
- 把用户名与密码直接保存到 Cookie 中,下次访问时检查 Cookie 中的用户名与密码,与数据库比较。这是一种比较危险的选择,一般不把密码等重要信息保存到 Cookie 中。
- 把密码加密后保存到 Cookie 中,下次访问时解密并与数据库比较,这种方案略微安全一些。
- 如果不希望保存密码,还可以把登录的时间戳保存到 Cookie 与数据库中,到时只验证用户名与登录时间戳就可以了。
这三种方案验证账号时都要查询数据库。
还有一种方案只在登录时查询一次数据库,以后访问验证登录信息时不再查询数据库:
- 把账号按照一定的规则加密后,连同秘钥一块保存到Cookie中。下次访问时只需要判断账号的加密规则是否正确即可。例如,把账号保存到名为 account 的 Cookie 中,把账号连同密钥用 MD1 算法加密后保存到名为 ssid 的 Cookie 中。验证时验证 Cookie 中的账号与密钥加密后是否与 Cookie 中的 ssid 相等。
Session
除了 Cookie,Web 应用程序中还经常用到 Session 来记录客户端状态。
什么是 Session
Session 是另一种记录用户状态的机制,不同于 Cookie 保存在客户端(浏览器),Session 保存在服务器上。
当客户端(浏览器)访问服务器时,服务器把客户端信息以某种形式记录在服务器上,客户端(浏览器)再次访问时只需要通过 Session 查找用户信息即可。
假如 Cookie 机制是通过”通行证”来确定用户身份,那么 Session 机制就是检查服务器上的”客户明细表”来确认客户身份。
Java 中的 Session
Session 对应的类为 javax.servlet.http.HttpSession
,每个访问者对应一个 Session 对象,且 Session 对象是客户端第一次请求服务器时创建的。Session 也是一种 k-v 属性对,通过 getAttribute(String key)
和 setAttribute(String key, Object value)
方法读写用户信息。
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 使用上比 Cookie 方便,但过多的 Session 存储在服务器内存中,会对服务器造成压力。
Session 的生命周期
Session 在用户第一次访问服务器时自动创建,生成后,只要用户继续访问,服务器就会更新最后的访问时间,并更新该 Session。用户每访问一次服务器,无论是否读写 Session,服务器都会认为该用户 Session “活跃 (active) “了一次。
Session 的有效期
为了防止内存溢出,服务器会把长时间未活跃的 Session 从内存中删除,这个时间就是 Session 的超时时间。
Session 的超时时间为 maxInactiveInterval
属性,可以通过 getMaxInactiveInterval()
方法获取,通过 setMaxInactiveInterval(long interval)
修改,另外,通过调用 invalidate()
方法也可以使 Session 失效。
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的最后活跃时间。 |
int getMaxInactiveInterval() | 返回Session的超时时间。单位为秒。超过该时间没有访问,服务器认为该Session失效。 |
void setMaxInactiveInterval(int second) | 设置Session的超时时间。 |
boolean isNew() | 返回该Session是否是新创建的。 |
void invalidate() | 使该Session失效。 |
Session 对浏览器的要求
虽然 Session 保存在服务器,但它的正常运行仍然需要客户端(浏览器)的支持,因为 Session 需要使用 Cookie 作为标识。HTTP 协议是无状态的,Session 不能依据 HTTP 连接来判断是否为同一用户,因此服务器向客户端(浏览器)发送一个名为 JSESSIONID
的 Cookie,其值为该 Session 的 id (即 HttpSession.getId()
的返回值),Session 根据该 Cookie 识别是否为同一用户。
如果客户端(浏览器)将 Cookie 禁用,则需要另外一种解决方案:URL 地址重写。
URL 地址重写
URL 地址重写是对客户端不支持 Cookie 的解决方案,其原理是将该用户 Session 的 id 重写到 URL 地址中,服务器解析 URL 获取 Session 的 id。
- Post link: http://example.com/2020/10/31/Cookie%20%E5%92%8C%20Session/
- Copyright Notice: All articles in this blog are licensed under unless otherwise stated.