引言:cookie和session诞生的背景。
我们知道web浏览器和服务器在网络上传输通信的过程中是采用HTTP协议的,而HTTP协议是一种无状态的协议,一旦数据交换完毕,客户端与服务器端的连接就会关闭,再次交换数据需要建立新的连接。这就意味着服务器无法从连接上跟踪会话,这个时候就要使用到会话跟踪技术了,那么什么是会话跟踪技术呢?会话是web应用中常见的技术,用来跟踪用户的整个会话过程,常见的会话跟踪技术就是cookie和session了,其中cookie通过在客户端记录信息从而确定用户的身份,而session是通过在服务器端记录信息从而确定用户的身份。
1. cookie会话机制
Cookie实际上是一小段的文本信息。客户端请求服务器,如果服务器需要记录该客户端用户的状态,就使用response返回向客 户端浏览器颁发一个Cookie。客户端浏览器会把Cookie保存起来。当客户端浏览器再次请求该网站时,客户端浏览器把请求的网址连同该Cookie一同提交给服务器。服务器检查该Cookie,以此来辨认用户状态。服务器还可以根据需要修改Cookie的内容。
Cookie是在客户端保持会话状态的方案。
1.1 cookie的属性
cookie的属性包括名字,值,过期时间,路径和域,下面对属性进行相关的介绍:
Name: 该Cookie的名称。Cookie一旦创建,名称便不可更改
Value: 该Cookie的值。如果值为Unicode字符,需要为字符编码。如果值为二进制数据,则需要使用BASE64编码
Domain: 可以访问该Cookie的域名。如果设置为“.baidu.com”,则所有以“baidu.com”结尾的域名都可以访问该Cookie。注意第一个字符必须为“.”,不是"baidu.com"
Path: 该Cookie的使用路径。如果设置为“/xx/”,则只有contextPath为“/xx”的程序可以访问该Cookie。如果设置为“/”,则本域名下contextPath都可以访问该Cookie。注意最后一个字符必须为“/”
Expires/Max-Age:该Cookie失效的时间,单位秒。如果为正数,则该Cookie在maxAge秒之后失效。如果为负数,该Cookie为临时Cookie,关闭浏览器即失效,浏览器也不会以任何形式保存该Cookie。如果为0,表示删除该Cookie。默认为–1
Secure: 该Cookie是否仅被使用安全协议传输。安全协议。安全协议有HTTPS,SSL等,在网络上传输数据之前先将数据加密。默认为false
1.2 Cookie的不可跨域名性
很多网站都会使用Cookie。例如,Google会向客户端颁发Cookie,Baidu也会向客户端颁发Cookie。那浏览器访问Google会不会也携带上Baidu颁发的Cookie呢?或者Google能不能修改Baidu颁发的Cookie呢?
答案是否定的。Cookie具有不可跨域名性。根据Cookie规范,浏览器访问Google只会携带Google的Cookie,而不会携带Baidu的Cookie。Google也只能操作Google的Cookie,而不能操作Baidu的Cookie。
Cookie在客户端是由浏览器来管理的。浏览器能够保证Google只会操作Google的Cookie而不会操作 Baidu的Cookie,从而保证用户的隐私安全。浏览器判断一个网站是否能操作另一个网站Cookie的依据是域名。Google与Baidu的域名 不一样,因此Google不能操作Baidu的Cookie。
需要注意的是,虽然网站images.google.com与网站www.google.com同属于Google,但是域名不一样,二者同样不能互相操作彼此的Cookie。下面是Cookie的源代码。
package javax.servlet.http; import java.io.Serializable; import java.text.MessageFormat; import java.util.Locale; import java.util.ResourceBundle; public class Cookie implements Cloneable, Serializable { private static final long serialVersionUID = -6454587001725327448L; private static final String TSPECIALS; private static final String LSTRING_FILE = "javax.servlet.http.LocalStrings"; private static ResourceBundle lStrings = ResourceBundle.getBundle("javax.servlet.http.LocalStrings"); private String name; private String value; private String comment; private String domain; private int maxAge = -1; private String path; private boolean secure; private int version = 0; private boolean isHttpOnly = false; public Cookie(String name, String value) { if (name != null && name.length() != 0) { if (this.isToken(name) && !name.equalsIgnoreCase("Comment") && !name.equalsIgnoreCase("Discard") && !name.equalsIgnoreCase("Domain") && !name.equalsIgnoreCase("Expires") && !name.equalsIgnoreCase("Max-Age") && !name.equalsIgnoreCase("Path") && !name.equalsIgnoreCase("Secure") && !name.equalsIgnoreCase("Version") && !name.startsWith("$")) { this.name = name; this.value = value; } else { String errMsg = lStrings.getString("err.cookie_name_is_token"); Object[] errArgs = new Object[]{name}; errMsg = MessageFormat.format(errMsg, errArgs); throw new IllegalArgumentException(errMsg); } } else { throw new IllegalArgumentException(lStrings.getString("err.cookie_name_blank")); } } public void setComment(String purpose) { this.comment = purpose; } public String getComment() { return this.comment; } public void setDomain(String domain) { this.domain = domain.toLowerCase(Locale.ENGLISH); } public String getDomain() { return this.domain; } public void setMaxAge(int expiry) { this.maxAge = expiry; } public int getMaxAge() { return this.maxAge; } public void setPath(String uri) { this.path = uri; } public String getPath() { return this.path; } public void setSecure(boolean flag) { this.secure = flag; } public boolean getSecure() { return this.secure; } public String getName() { return this.name; } public void setValue(String newValue) { this.value = newValue; } public String getValue() { return this.value; } public int getVersion() { return this.version; } public void setVersion(int v) { this.version = v; } private boolean isToken(String value) { int len = value.length(); for(int i = 0; i < len; ++i) { char c = value.charAt(i); if (c < ' ' || c >= 127 || TSPECIALS.indexOf(c) != -1) { return false; } } return true; } public Object clone() { try { return super.clone(); } catch (CloneNotSupportedException var2) { throw new RuntimeException(var2.getMessage()); } } public void setHttpOnly(boolean isHttpOnly) { this.isHttpOnly = isHttpOnly; } public boolean isHttpOnly() { return this.isHttpOnly; } static { if (Boolean.valueOf(System.getProperty("org.glassfish.web.rfc2109_cookie_names_enforced", "true")).booleanValue()) { TSPECIALS = "/()<>@,;:\\\"[]?={} \t"; } else { TSPECIALS = ",; "; } } }
1.3 Cookie的安全属性
HTTP协议不仅是无状态的,而且是不安全的。使用HTTP协议的数据不经过任何加密就直接在网络上传播,有被截获的可能。使用HTTP协议传输很机密的内容是一种隐患。如果不希望Cookie在HTTP等非安全协议中传输,可以设置Cookie的secure属性为 true。浏览器只会在HTTPS和SSL等安全协议中传输此类Cookie。下面的代码设置secure属性为true:
Cookie cookie = new Cookie("sex", "male"); // 新建Cookie
cookie.setSecure(true); // 设置安全属性
response.addCookie(cookie); // 输出到客户端
提示:secure属性并不能对Cookie内容加密,因而不能保证绝对的安全性。如果需要高安全性,需要在程序中对Cookie内容加密、解密,以防泄密。
2. session会话机制
Session是另一种记录客户端用户状态的机制,不同的是Cookie保存在客户端浏览器中,而Session保存在服务器上。客户端浏览器访问服务器的时候,服务器把客户端信息以某种形式记录在服务器上。这就是Session。客户端浏览器再次访问时只需要从该Session中查找该客户的状态就可以了。
如果说Cookie机制是通过检查客户身上的“通行证”来确定客户身份的话,那么Session机制就是通过检查服务器上的“客户明细表”来确认客户身份。Session相当于程序在服务器上建立的一份客户档案,客户来访的时候只需要查询客户档案表就可以了。
Session对应的类为javax.servlet.http.HttpSession类。每个来访者对应一个Session对象,所有该客户的状态信息都保存在这个Session对象里。Session对象是在客户端第一次请求服务器的时候创建的。 Session也是一种key-value的属性对,通过getAttribute(Stringkey)和setAttribute(String key,Objectvalue)方法读写客户状态信息。Servlet里通过request.getSession()方法获取该客户的 Session。
package javax.servlet.http; import java.util.Enumeration; import javax.servlet.ServletContext; public interface HttpSession { long getCreationTime(); String getId(); long getLastAccessedTime(); ServletContext getServletContext(); void setMaxInactiveInterval(int var1); int getMaxInactiveInterval(); /** @deprecated */ HttpSessionContext getSessionContext(); Object getAttribute(String var1); /** @deprecated */ Object getValue(String var1); EnumerationgetAttributeNames(); /** @deprecated */ String[] getValueNames(); void setAttribute(String var1, Object var2); /** @deprecated */ void putValue(String var1, Object var2); void removeAttribute(String var1); /** @deprecated */ void removeValue(String var1); void invalidate(); boolean isNew(); }
session运行原理:
浏览器第一次访问服务器时会创建一个session对象并返回一个JSESSIONID=ID的值,创建一个Cookie对象key为JSSIONID,value为ID的值,将这个Cookie写回浏览器。
浏览器在第二次访问服务器的时候携带Cookie信息JSESSIONID=ID的值,如果该JSESSIONID的session已经销毁,那么会重新创建一个新的session再返回一个新的JSESSIONID通过Cookie返回到浏览器。
session是基于Cookie技术实现,重启浏览器后再次访问原有的连接依然会创建一个新的session,因为Cookie在关闭浏览器后就会消失,但是原来服务器的Session还在,只有等到了销毁的时间会自动销毁。
一般这个值会有个时间限制,超时后毁掉这个值,默认30分钟。
当用户在应用程序的 Web页间跳转时,存储在 Session 对象中的变量不会丢失而是在整个用户会话中一直存在下去。
Session的实现方式和Cookie有一定关系。建立一个连接就生成一个session id,打开几个页面就好几个了,这里就用到了Cookie,把session id存在Cookie中,每次访问的时候将Session id带过去就可以识别了。
2.1 Session属性的相关介绍:
void setAttribute(String attribute, Object value):设置Session属性。value参数可以为任何Java Object。通常为Java Bean。value信息不宜过大
String getAttribute(String attribute):返回Session属性
Enumeration getAttributeNames():void removeAttribute(String attribute)
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的超时时间。单位为秒
boolean isNew():返回该Session是否是新创建的
void invalidate():使该Session失效
3. 两者的区别
3.1 存储数据大小限制:session 能够存储任意的 java 对象,cookie 只能存储 String 类型的对象
3.2 存储地址不同(一个在客户端一个在服务端)。Cookie不安全,Session相对安全。
3.3 Session是保存在服务器端上会存在一段时间才会消失,如果session过多会增加服务器的压力
3.4 域的支持范围不一样,比方说a.com的Cookie在a.com下都能用,而www.a.com的Session在api.a.com下都不能用,解决这个问题的办法是JSONP或者跨域资源共享。