会话技术之Cookie
文章中所有源代码都在我的这个GitHub
的公开库--->servlet。Star
来一个好吗?秋梨膏!
Cookie 详解
国际惯例,学什么之前都得 HelloWorld 一下。
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//设置编码
resp.setContentType("text/html; charset=UTF-8");
//new 一个cookie
Cookie cookie =new Cookie("username","ling");
//加入这个 cookie
resp.addCookie(cookie);
//输出相关提示
resp.getWriter().write("Hello World! 写入了一个cookie");
}
调试模式下,可以看到已经存入自定义的 Cookie 。
Cookie 类的⽅法简述:
-
Cookie(String name,String value) :构造方法
-
setComment(String purpose) 与 getComment(): 用于注释,如果浏览器向用户展示 Cookie,需要使用到。
-
setValue(String newValue) 与 getValue() ⽅法:为 cookie 赋值或者取值。
-
setMaxAge(int expiry) 与 getMaxAge()⽅法 :设置,获取 Cookie 的有效期。
-
setPath(String uri) 与 getPath() ⽅法:Cookie 的path 属性决定允许访问Cookie 的路径。⼀般地,Cookie 发布出来,整个⽹⻚的资源都可以使⽤。现在我只想某个 Servlet 可以获取到 Cookie,其他的资源不能获取。
-
setDomain(String pattern) 与getDomain() ⽅法 :想要同级的域名可以共享Cookie 需要用到该方法。
-
getName⽅法 :取得该cookie 的 name ,name 在创建后不可以修改。
-
setSecure(boolean flag) :HTTP协议是⽆状态的,还是不安全的!如果不想Cookie在⾮安全协议中传输,设置Cookie的secure属性为true,浏览器只会在 HTTPS 和 SSL 等安全协议中传输该 Cookie。设置secure属性不会将Cookie的内容加密。如果想要保证安全,最好使⽤ MD5 算法加密。
Cookie 保存中文(编码问题)
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//设置编码
resp.setContentType("text/html; charset=UTF-8");
//new 一个cookie
//这里与此前设值不同需要进行编码处理
Cookie newCookie =new Cookie("Chinese-Cookie", URLEncoder.encode("中文Cookie","UTF-8"));
//加入这个 cookie
resp.addCookie(newCookie);
//取出该中文 Cookie 自然是需要解码的
Cookie[] cookies = req.getCookies();
for (Cookie cookie: cookies
) {
String name = cookie.getName();
String values = URLDecoder.decode(cookie.getValue(),"UTF-8");
resp.getWriter().write(name +"-----"+values+"<br>");
}
//输出相关提示
resp.getWriter().write("Hello World! 写入了一个中文Cookie");
}
页面是可以看到中文 Cookie 的正确输出,上面还有HelloWorld 时候保存下来的 Cookie。
当然在浏览器保存的值是编码后的 Cookie 。
Cookie 的有效期(删除)
- 正值(n)表示该cookie将在n秒后过期。请注意,该值是Cookie过期的最长期限,而不是Cookie的当前期限,单位是 秒。
- 负值表示cookie不会持久存储,并且在网络浏览器退出时将被删除。
- 零值将导致cookie 被删除。这是删除Cookie 的一个途径。
//设置有效期
newCookie.setMaxAge(60 * 60 * 24 *2); //以秒为单位,设置期限为两天,现在是2020-09-04
注意要在添加该 Cookie 之前设置有效期,最后记得加入该 Cookie。
此外,不知道你是否注意到
原来 HelloWorld 时候保存下来的 Cookie ,我们并没有设置它的有效期,浏览器显示它的有效期是 Session ,这就意味着,这是默认的值,仅限于此次会话,关闭浏览器该 Cookie 就会失效。
Cookie 的域名
不知道你收否注意到:
在此之前,首先要明确Cookie 具有不可跨域名性,访问不同的网站,浏览器会颁发不同的 Cookie 给服务器,也不会修改别的网站的 Cookie。即使⼀级域名相同,⼆级域名不同,也不能获取到 Cookie。
但是一个庞大的网站会像一个树状结构,必定会细分有二级域名,如果这时候,需要二级域名也可以访问到相同一级域名下的 Cookie ,需要使用到 setDomain(String pattern) 方法。再次强调,要在添加该 Cookie 之前设置有效期,最后记得加入该 Cookie 。
//设置运行访问Cookie 的域名
newCookie.setDomain(".ling.com"); //取值规定为".域名"
此时 www.blog.ling.com 也可以访问到 www.ling.com 发布的Cookie。
个人测试可以使用 Tomcat 配置不同的临时域名进行请求测试,具体配置可以参照 Tomcat 简单配置使用,基本工作原理 中的配置临时域名部分进行配置。
Cookie 的路径
在浏览器中 Domain 旁边是 Path 属性。
可以在浏览器中看到,每一个 Cookie 都会有自己的路径。path 属性决定允许的访问路径。一般来说 Cookie 一旦发布,相同域名的网站都可以访问到。
现在有一需求是,我只想,某些路径以及该路径下的子路径可以访问到该 Cookie。这时候可以使用 setPath(String uri) 方法。
假设你的浏览器当前已经有了两个Cookie:
- c1:name=id; value=itcast; path=/ling/;
- c2:name=name; value=qdmmy6; path=/ling/servlet/。
当访问http://localhost/ling/*时,请求头中会包含c1,而不会包含c2。
当访问http://localhost/ling/servlet/*时,请求头中会包含c1和c2。
也就是说,在访问子路径时,会包含其父路径的Cookie,而在访问父路径时,不包含子路径的Cookie。
此前的项目名笔者设置为空,现在设置为 /ling ,以便验证结论是否正确
Cookie的SetPath设置cookie的路径,这个路径直接决定服务器的请求是否会从浏览器中加载某些cookie。
首先默认情况如果不设置cookie的path,默认是 /项目名称/当前路径的上一层地址如:请求路径:/cookie_demo/servlet/login, cookie的路径:/cookie_demo/servlet
如果我们设置path,如果当前访问的路径包含了cookie的路径(当前访问路径在cookie路径基础上要比cookie的范围小)cookie就会加载到request对象之中。
先不进行访问路径设置,访问 http://localhost:8080/ling/cookies/testCookies ,查看默认路径。
就是 /项目名称/当前路径的上一层地址 。
再访问 http://localhost:8080/ling/request/getServlet ,验证是否能访问该Cookie。
在未进行设置的情况下,当然是不行的。
下面进行设置:
//设置访问路径
newCookie.setPath("/ling/"); // /ling/ 路径下所有子路径都可以访问
先访问 http://localhost:8080/ling/cookies/testCookies 设置Cookie。
再访问 http://localhost:8080/ling/request/getServlet ,验证是否能访问该Cookie。
Cookie 的修改
删除,修改 Cookie 时,新建的 Cookie 除了 value 、maxAge 之外的所有属性都要与原 Cookie 相同。否则浏览器将视为不同的 Cookie,不予覆盖,导致删除修改失败!
为什么不把修改放在删除后说,为了验证在除了 value 、maxAge 之外的其他属性不同时,是否能删除原 Cookie。
这里的其他属性,就以 Path 属性举例。
注意前提条件
已知,在浏览器已经将 name : Chinese-Cookie ;value:%E4%B8%AD%E6%96%87Cookie---2 ;Path :/ling/cookies . 的 Cookie1 保存到硬盘上。
现在 new 一个 Cookie2 将 Path 设置为 /ling/ 再将其删除(即 setMaxAge(0) )。
这次的访问路径:http://localhost:8080/ling/cookies/testCookies
//设置编码
resp.setContentType("text/html; charset=UTF-8");
//new 一个cookie
//这里与此前设值不同需要进行编码处理
Cookie newCookie =new Cookie("Chinese-Cookie", URLEncoder.encode("中文Cookie---3,我是Cookie2 。。。","UTF-8"));
//cookie 的各种设置
//设置有效期
newCookie.setMaxAge(0); //设置期限为0 ,试图删除 Cookie1
//设置访问路径
newCookie.setPath("/ling/"); // /ling/ 路径下所有子路径都可以访问
//加入这个 cookie
resp.addCookie(newCookie);
//取出该中文 Cookie 自然是需要解码的
Cookie[] cookies = req.getCookies();
for (Cookie cookie: cookies
) {
String name = cookie.getName();
String values = URLDecoder.decode(cookie.getValue(),"UTF-8");
resp.getWriter().write(name +"-----"+values+"<br>");
}
//输出相关提示
resp.getWriter().write("Hello World! 写入了一个中文Cookie");
预期是不能删除 Cookie1 ,得到的将是 Cookie1 ,因为 Cookie2 刚刚 new 出来虽然加了进去,可是已经 setMaxAge(0) ,就像这九子夺嫡,四阿哥已经登基,其他皇子想上台面,就像 Cookie2想输出在页面,自然也是轮不到他。
这次来真的,删掉 Cookie1 。
//设置编码
resp.setContentType("text/html; charset=UTF-8");
//new 一个cookie
//这里与此前设值不同需要进行编码处理
Cookie newCookie =new Cookie("Chinese-Cookie", URLEncoder.encode("中文Cookie---3,我是Cookie2 。。。","UTF-8"));
//cookie 的各种设置
//设置有效期
newCookie.setMaxAge(0); //设置期限为0 ,试图删除 Cookie1
//加入这个 cookie
resp.addCookie(newCookie);
//取出该中文 Cookie 自然是需要解码的
Cookie[] cookies = req.getCookies();
for (Cookie cookie: cookies
) {
String name = cookie.getName();
String values = URLDecoder.decode(cookie.getValue(),"UTF-8");
resp.getWriter().write(name +"-----"+values+"<br>");
}
//输出相关提示
resp.getWriter().write("Hello World! 写入了一个中文Cookie");
唉?你这没设置路径啊?
噢,你得结合上面说的访问路径和 Cookie 的路径这一小节来看,因为访问路径是 http://localhost:8080/ling/cookies/testCookies ,这次的 Cookie2 默认Path 就是 /ling/cookies
可以看到,是可以获取到在硬盘里的 Cookie1 的,输出在页面的还是 Cookie1 ,Cookie2 终究上不了台面,只是 Cookie1 被 Cookie2 覆盖以后,两大高手同归于尽,浏览器再无 Cookie1 Cookie2 。
Cookie 的安全
HTTP协议不仅仅是⽆状态的,⽽且是不安全的!如果不希望Cookie在⾮安全协议中传输,可以设置Cookie的secure属性为true,浏览器只会在HTTPS和SSL等安全协议中传输该Cookie。
//设置只在安全协议下传输 Cookie
newCookie.setSecure(true);
当然了,设置secure属性不会将Cookie的内容加密。如果想要保证安全,最好使⽤md5算法加密。
Cookie 的简单应用
显示用户上次访问时间
首先明确需求:
- 第一次登录,记录下时间;
- 第n次登录(n >= 2),显示前一次登录时间,并更新时间。
要判断是否是第一次登录,可以通过判断是否含有指定 Cookie 来决定。
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//设置时间样式
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
//编码
resp.setContentType("text/html;charset=UTF-8");
PrintWriter writer = resp.getWriter();
//获取Cookie
Cookie[] cookies = req.getCookies();
//循环取出 Cookie
for(int i = 0 ; cookies.length >0 && i < cookies.length -1 ; i++){
//判断是否含有所需要的的 Cookie--"login-time"
if(cookies[i].getName().equals("login-time")){
//注意进行编码
writer.write("欢迎您,您上次登录的时间是: "+ URLDecoder.decode(cookies[i].getValue(),"UTF-8"));
//有则显示上一次时间,注意要解码,并 new Cookie() 更新时间,以便下一次显示使用
cookies[i].setValue(URLEncoder.encode(simpleDateFormat.format(new Date()),"UTF-8"));
//设置有效期
cookies[i].setMaxAge(2000);
resp.addCookie(cookies[i]);
return ;
}
}
//没有则显示是第一次登录,设置登录时间
Cookie cookie =new Cookie("login-time", URLEncoder.encode(simpleDateFormat.format(new Date()),"UTF-8"));
cookie.setMaxAge(2000);
resp.addCookie(cookie);
writer.write("欢迎您,您是第一次登录。");
}
第一次登录:
刷新一下:
需要注意的几个点
-
编码问题:需要设置编码,否则输出的中文会出现乱码。
-
更新 Cookie 要设置Cookie 的有效期,否则更新的 Cookie 有效期默认为 Session ,关闭浏览器后,Cookie将会被销毁,不会被保存在硬盘。
-
Cookie 不支持特殊符号。下图表示 Cookie 的 Value 中有空格,会报服务器的错。解决方案有两个:
- 把空格换成别的符号,比如下划线 _ ,横杆 - 等等。
- 如上代码所示,进行编码,不过注意,取值的时候也要进行解码。
web.xml
参照 HttpServletRespnse 对象相关基本应用 进行配置 Servlet 。