cookie和session详解
Cookie 概述
Cookie是什么?
cookie 和session 的区别:
1、cookie数据存放在客户的浏览器上,session数据放在服务器上。
4、单个cookie保存的数据不能超过4K,很多浏览器都限制一个站点最多保存20个cookie。
1.1.2 记录用户访问次数
Java中把Cookie封装成了javax.servlet.http.Cookie类。每个Cookie都是该Cookie类的对象。服务器通过操作Cookie类对象对客户端Cookie进行操作。通过request.getCookie()获取客户端提交的所有Cookie(以Cookie[]数组形式返回),通过response.addCookie(Cookiecookie)向客户端设置Cookie。
Cookie对象使用key-value属性对的形式保存用户状态,一个Cookie对象保存一个属性对,一个request或者response同时使用多个Cookie。因为Cookie类位于包javax.servlet.http.*下面,所以JSP中不需要import该类。
1.1.3 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。
注意:用户登录网站www.google.com之后会发现访问images.google.com时登录信息仍然有效,而普通的Cookie是做不到的。这是因为Google做了特殊处理。
1.1.4 Unicode编码:保存中文
中文与英文字符不同,中文属于Unicode字符,在内存中占4个字符,而英文属于ASCII字符,内存中只占2个字节。Cookie中使用Unicode字符时需要对Unicode字符进行编码,否则会乱码。
提示:Cookie中保存中文只能编码。一般使用UTF-8编码即可。不推荐使用GBK等中文编码,因为浏览器不一定支持,而且JavaScript也不支持GBK编码。
属 性 名 |
描 述 |
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”结尾的域名都可以访问该Cookie。注意第一个字符必须为“.” |
String comment |
该Cookie的用处说明。浏览器显示Cookie信息的时候显示该说明 |
int version |
该Cookie使用的版本号。0表示遵循Netscape的Cookie规范,1表示遵循W3C的RFC 2109规范 |
HttpOnly
|
确定页面js或其他活动内容是否可访问此 Cookie。若此属性为true,则只有在http请求头中会带有此cookie的信息,而不能通过document.cookie来访问此cookie。
|
Expires
|
获取或设置作为 DateTime 的 Cookie 过期日期和时间。
|
Expired
|
获取或设置 Cookie 的当前状态。
|
CommentUri
|
获取或设置服务器可通过 Cookie 来提供的 URI 注释。
|
Port
|
获取或设置此 Cookie 适用于的 TCP 端口的列表。
|
TimeStamp
|
获取此 Cookie 作为 DateTime 发出的时间。
|
Discard
|
获取或设置由服务器设置的丢弃标志。
|
1.1.7 Cookie的有效期
Cookie cookie = new Cookie("username","helloweenvsfei"); // 新建Cookie cookie.setMaxAge(Integer.MAX_VALUE); // 设置生命周期为MAX_VALUE s response.addCookie(cookie); // 输出到客户端
如果maxAge为负数,则表示该Cookie仅在本浏览器窗口以及本窗口打开的子窗口内有效,关闭窗口后该Cookie即失效。maxAge为负数的Cookie,为临时性Cookie,不会被持久化,
如果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是否过期。
1.1.8 Cookie的修改、删除
Cookie并不提供修改、删除操作。如果要修改某个Cookie,只需要新建一个同名的Cookie,添加到response中覆盖原来的Cookie。
1.1.9 Cookie的域名
Cookie cookie = new Cookie("time","20080808"); // 新建Cookie cookie.setDomain(".helloweenvsfei.com"); // 设置域名 cookie.setPath("/"); // 设置路径 cookie.setMaxAge(Integer.MAX_VALUE); // 设置有效期 response.addCookie(cookie); // 输出到客户端 或者:document.cookie = "username=Darren;path=/;domain=helloweenvsfei.com"
读者可以修改本机C:\WINDOWS\system32\drivers\etc下的hosts文件来配置多个临时域名,然后使用setCookie.jsp程序来设置跨域名Cookie验证domain属性。
1.1.10 Cookie的路径(访问过滤)
Cookie cookie = new Cookie("time","20080808"); // 新建Cookie cookie.setPath("/session/"); // 设置路径 response.addCookie(cookie); // 输出到客户端
设置为“/”时允许所有路径使用Cookie。path属性需要使用符号“/”结尾。name相同但domain不同的两个Cookie也是两个不同的Cookie。
1.1.11 Cookie的安全属性
Cookie cookie = new Cookie("time", "20080808"); // 新建Cookie cookie.setSecure(true); // 设置安全属性 response.addCookie(cookie); // 输出到客户端
提示:secure属性并不能对Cookie内容加密,因而不能保证绝对的安全性。如果需要高安全性,需要在程序中对Cookie内容加密、解密,以防泄密。
1.1.12 JavaScript操作Cookie
<script>document.write(document.cookie);</script>
在输入cookie信息时不能包含空格,分号,逗号等特殊符号,而在一般情况下,cookie 信息的存储都是采用未编码的方式。所以,在设置 cookie 信息以前要先使用escape()函数将 cookie 值信息进行编码,在获取到 cookie 值得时候再使用unescape()函数把值进行转换回来。如设置cookie时:
document.cookie = name + "="+ escape (value)
再看看基础用法时提到过的getCookie()内的一句:
1.1.13 案例:永久登录
这几种方案验证账号时都要查询数据库。
//controller处理: @RequestParam(defaultValue = "", required = false, value = "action")String action,//操作类型 @RequestParam(defaultValue = "", required = false, value = "account")String account,//账号 @RequestParam(defaultValue = "", required = false, value = "password")String password,//密码 @RequestParam(defaultValue = "-1", required = false, value = "timeout")int timeout,//失效时间 JsonResultBean jsonResultBean=new JsonResultBean(true); String ssid = null;// SSID标识 if(timeout==1){//永久登录 timeout = Integer.MAX_VALUE; }else if(timeout==0){//30天有效 timeout = 30 *24 * 60 * 60; }else{//关闭浏览器则失效 timeout = -1;//-1为cookie默认有效期值,表示关闭浏览器即失效 } if("login".equals(action)){//如果为login动作 ssid =calcMD5(account + KEY); // 把账号、密钥使用MD1加密后保存 Cookie accountCookie = new Cookie("account", account);// 新建Cookie accountCookie.setMaxAge(timeout);// 设置有效期 resp.addCookie(accountCookie); // 输出到客户端 Cookie ssidCookie =new Cookie("ssid", ssid);// 新建Cookie ssidCookie.setMaxAge(timeout); // 设置有效期 resp.addCookie(ssidCookie); // 输出到客户端 }else if("logout".equals(action)){// 如果为logout退出动作 Cookie accountCookie = new Cookie("account", ""); accountCookie.setMaxAge(0);// 设置有效期为0,删除 Cookie ssidCookie =new Cookie("ssid", ""); // 新建Cookie,内容为空 ssidCookie.setMaxAge(0); // 设置有效期为0,删除 resp.addCookie(accountCookie); // 输出到客户端 resp.addCookie(ssidCookie); // 输出到客户端 } return jsonResultBean; //判断是否登录 private boolean checkLogin(HttpServletRequest req){ boolean isLogin = false;// 是否登录 String ssid = null;// SSID标识 String account = null;//账号 //先判断是否已经登录,如果登录则跳转到首页 if(req.getCookies() !=null){// 如果Cookie不为空 for(Cookie cookie :req.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都不为空 isLogin =ssid.equals(calcMD5(account + KEY));// 如果加密规则正确, 则视为已经登录 } return isLogin; } //MD5加密算法(加密算法可以考虑将登录时间作为动态变化的值传入一起加密实现动态加密) private static final String KEY =":cookie@helloweenvsfei.com";// 密钥 public final static String calcMD5(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 =s.getBytes();// 获取字节 MessageDigest mdTemp = MessageDigest.getInstance("MD5"); // 获取MD5 mdTemp.update(strTemp);// 更新数据 byte[] md =mdTemp.digest();// 加密 int j =md.length; // 加密后的长度 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; } }
页面: <table> <tr> <td>账号: </td> <td><input type="text"name="account" id="account" style="width:200px;"/></td> </tr> <tr> <td>密码: </td> <td><input type="password"name="password" id="password" style="width:200px;"/></td> </tr> <tr> <td>有效期: </td> <td> <input type="radio" name="timeout" value="-1" checked/> 关闭浏览器即失效 <br/> <input type="radio" name="timeout" value="0"/> 30天内有效 <br/> <input type="radio" name="timeout" value= "1"/> 永久有效 <br/> </td> </tr> <tr> <td><input type="button" value=" 登 录 " class= "button" id= "submit"></td> </tr> </table>
登录时可以选择登录信息的有效期:关闭浏览器即失效、30天内有效与永久有效。通过设置Cookie的age属性来实现,注意观察代码。运行效果如图1.7所示。
1.2 Session机制
除了使用Cookie,Web应用程序中还经常使用Session来记录客户端状态。Session是服务器端使用的一种记录客户端状态的机制,使用上比Cookie简单一些,相应的也增加了服务器的存储压力。
1.2.1 什么是Session
1.2.2 实现用户登录
session.setAttribute("loginTime", new Date()); // 设置Session中的属性
out.println("登录时间为:" +(Date)session.getAttribute("loginTime")); // 获取Session属性
request还可以使用getSession(boolean create)来获取Session。区别是如果该客户的Session不存在,request.getSession()方法会返回null,而getSession(true)会先创建Session再将Session返回。
DateFormat dateFormat = newSimpleDateFormat("yyyy-MM-dd"); // 日期格式化器 response.setCharacterEncoding("UTF-8"); // 设置request编码 Person[] persons = { // 基础数据,保存三个人的信息 new Person("Liu Jinghua","password1", 34, dateFormat.parse ("1982-01-01")), new Person("Hello Kitty","hellokitty", 23, dateFormat.parse ("1984-02-21")), new Person("Garfield", "garfield_pass",23, dateFormat.parse ("1994-09-12")), }; String message = ""; // 要显示的消息 if(request.getMethod().equals("POST")) { // 如果是POST登录 for(Person person :persons){ // 遍历基础数据,验证账号、密码 // 如果用户名正确且密码正确 if(person.getName().equalsIgnoreCase(request.getParameter("username"))&&person.getPassword().equals(request.getParameter("password"))) { // 登录成功,设置将用户的信息以及登录时间保存到Session session.setAttribute("person", person); // 保存登录的Person session.setAttribute("loginTime", new Date()); // 保存登录的时间 response.sendRedirect(request.getContextPath() + "/welcome.jsp"); return; } } message = "用户名密码不匹配,登录失败。"; // 登录失败 }
登录界面验证用户登录信息,如果登录正确,就把用户信息以及登录时间保存进Session,然后转到欢迎页面welcome.jsp。welcome.jsp中从Session中获取信息,并将用户资料显示出来。
DateFormat dateFormat = newSimpleDateFormat("yyyy-MM-dd"); // 日期格式化器 Person person =(Person)session.getAttribute("person"); // 获取登录的person Date loginTime =(Date)session.getAttribute("loginTime"); // 获取登录时间 // ... 部分HTML代码略 <table> <tr><td>您的姓名:</td> <td><%= person.getName()%></td> </tr> <tr><td>登录时间:</td> <td><%= loginTime%></td> </tr> <tr><td>您的年龄:</td> <td><%= person.getAge()%></td> </tr> <tr><td>您的生日:</td> <td><%=dateFormat.format(person.getBirthday()) %></td> </tr> </table>
注意程序中Session中直接保存了Person类对象与Date类对象,使用起来要比Cookie方便。
提示:Session的使用比Cookie方便,但是过多的Session存储在服务器内存中,会对服务器造成压力。
1.2.3 Session的生命周期
1.2.4 Session的有效期
Session的超时时间为maxInactiveInterval属性,可以通过对应的getMaxInactiveInterval()获取,通过setMaxInactiveInterval(longinterval)修改。
Session的超时时间也可以在web.xml中修改。另外,通过调用Session的invalidate()方法可以使Session失效。
1.2.5 Session的常用方法
Session中包括各种方法,使用起来要比Cookie方便得多。Session的常用方法如表1.2所示。
表1.2 HttpSession的常用方法
方 法 名 |
描 述 |
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-timeout>参数的单位为分钟,而setMaxInactiveInterval(int s)单位为秒。
1.2.6 Session对浏览器的要求
1.2.7 URL地址重写
<td>
</td>
该方法会自动判断客户端是否支持Cookie。如果客户端支持Cookie,会将URL原封不动地输出来。如果客户端不支持Cookie,则会将用户Session的id重写到URL中。重写后的输出可能是这样的:
<td>
</td>
即在文件名的后面,在URL参数的前面添加了字符串“;jsessionid=XXX”。其中XXX为Session的id。分析一下可以知道,增添的jsessionid字符串既不会影响请求的文件名,也不会影响提交的地址栏参数。用户单击这个链接的时候会把Session的id通过URL提交到服务器上,服务器通过解析URL地址获得Session的id。
response.sendRedirect(response.encodeRedirectURL(“administrator.jsp”));
return;
}
效果跟response.encodeURL(String url)是一样的:如果客户端支持Cookie,生成原URL地址,如果不支持Cookie,传回重写后的带有jsessionid字符串的地址。
打开项目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>
<input type="button" onclick="WriteCookie();" value="WriteCookie" /> <script type="text/javascript"> function WriteCookie() { var cookie = "cookie_js=22222222; path=/"; document.cookie = cookie; } </script>
以上代码中'cookie_js '表示 cookie 名称,'22222222 '表示这个名称对应的值。假设 cookie 名称并不存在,那么就是创建一个新的 cookie;如果存在就是修改了这个 cookie 名称对应的值。如果要多次创建 cookie ,重复使用这个方法即可。
再来看一下如何使用JS读取Cookie吧。请参考如下代码:
<input type="button" onclick="ReadCookie();" value="ReadCookie" /> <script type="text/javascript"> function ReadCookie() { alert(document.cookie); } </script>
仍然是访问document.cookie,不过,这次我们得到却是全部的Cookie值,每个Key/Value项用分号分开,中间则用等号分开。 所以, 如果您想在JS中读取Cookie,一定要按照这个规则来拆分并解析
jQuery.cookie = function(name, value, options) { if (typeof value != 'undefined') { // name and value given, set cookie options = options || {}; if (value === null) { value = ''; options.expires = -1; } var expires = ''; if (options.expires && (typeof options.expires == 'number' || options.expires.toUTCString)) { var date; if (typeof options.expires == 'number') { date = new Date(); date.setTime(date.getTime() + (options.expires * 24 * 60 * 60 * 1000)); } else { date = options.expires; } expires = '; expires=' + date.toUTCString(); // use expires attribute, max-age is not supported by IE } var path = options.path ? '; path=' + options.path : ''; var domain = options.domain ? '; domain=' + options.domain : ''; var secure = options.secure ? '; secure' : ''; document.cookie = [name, '=', encodeURIComponent(value), expires, path, domain, secure].join(''); } else { // only name given, get cookie var cookieValue = null; if (document.cookie && document.cookie != '') { var cookies = document.cookie.split(';'); for (var i = 0; i < cookies.length; i++) { var cookie = jQuery.trim(cookies[i]); // Does this cookie string begin with the name we want? if (cookie.substring(0, name.length + 1) == (name + '=')) { cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); break; } } } return cookieValue; } };
注意哦:前面我们看到了HttpCookie有个HttpOnly属性,如果它为true,那么JS是读不到那个Cookie的,也就是说: 我们如果在服务端生成的Cookie不希望在JS中能被访问,可以在写Cookie时,
function getCookie(c_name){ if (document.cookie.length>0){ //先查询cookie是否为空,为空就return "" c_start=document.cookie.indexOf(c_name + "=") //通过String对象的indexOf()来检查这个cookie是否存在,不存在就为 -1 if (c_start!=-1){ c_start=c_start + c_name.length+1 //最后这个+1其实就是表示"="号啦,这样就获取到了cookie值的开始位置 c_end=document.cookie.indexOf(";",c_start) //从c_start位置开始检索,看后面的字符串中是否包含";",包含则返回位置索引,不包含则返回-1, 这句是为了得到值的结束位置。因为需要考虑是否是最后一项,所以通过";"号是否存在来判断 if (c_end==-1) c_end=document.cookie.length //如果不是以分号结尾则表示是cookie文件的最后一项,则读取的结尾索引就是cookie的长度 return unescape(document.cookie.substring(c_start,c_end)) //通过substring()得到了值。想了解unescape()得先知道escape()是做什么的,都是很重要的基础,想了解的可以搜索下,在文章结尾处也会进行讲解cookie编码细节 } } return "" } function writeCookie(name,value,time){//时间单位是秒 var date = new Date(); date.setTime(date.getTime() + time * 1000); var expires = '; expires=' + date.toUTCString(); document.cookie = name+"="+value+expires; }