会话技术之 Cookie

会话技术之Cookie


文章中所有源代码都在我的这个GitHub的公开库--->servletStar来一个好吗?秋梨膏!


国际惯例,学什么之前都得 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 。

image-20200904094045754

  • 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 算法加密。

 @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。

image-20200904100447270

当然在浏览器保存的值是编码后的 Cookie 。

image-20200904100822056

  • 正值(n)表示该cookie将在n秒后过期。请注意,该值是Cookie过期的最长期限,而不是Cookie的当前期限,单位是 秒。
  • 负值表示cookie不会持久存储,并且在网络浏览器退出时将被删除。
  • 零值将导致cookie 被删除。这是删除Cookie 的一个途径。
	//设置有效期 
newCookie.setMaxAge(60 * 60 * 24 *2);  //以秒为单位,设置期限为两天,现在是2020-09-04

image-20200904101210104

注意要在添加该 Cookie 之前设置有效期,最后记得加入该 Cookie。

此外,不知道你是否注意到

image-20200904113206810

原来 HelloWorld 时候保存下来的 Cookie ,我们并没有设置它的有效期,浏览器显示它的有效期是 Session ,这就意味着,这是默认的值,仅限于此次会话,关闭浏览器该 Cookie 就会失效。

不知道你收否注意到:

image-20200904132313714

在此之前,首先要明确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 简单配置使用,基本工作原理 中的配置临时域名部分进行配置。

在浏览器中 Domain 旁边是 Path 属性。

image-20200904131205924

可以在浏览器中看到,每一个 Cookie 都会有自己的路径。path 属性决定允许的访问路径。一般来说 Cookie 一旦发布,相同域名的网站都可以访问到。

现在有一需求是,我只想,某些路径以及该路径下的子路径可以访问到该 Cookie。这时候可以使用 setPath(String uri) 方法。

假设你的浏览器当前已经有了两个Cookie:

  1. c1:name=id; value=itcast; path=/ling/;
  2. c2:name=name; value=qdmmy6; path=/ling/servlet/。

当访问http://localhost/ling/*时,请求头中会包含c1,而不会包含c2。

当访问http://localhost/ling/servlet/*时,请求头中会包含c1和c2。

也就是说,在访问子路径时,会包含其父路径的Cookie,而在访问父路径时,不包含子路径的Cookie。

此前的项目名笔者设置为空,现在设置为 /ling ,以便验证结论是否正确

image-20200904150035302

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 ,查看默认路径。

image-20200904145752882

就是 /项目名称/当前路径的上一层地址 。

再访问 http://localhost:8080/ling/request/getServlet ,验证是否能访问该Cookie。

image-20200904150138865

在未进行设置的情况下,当然是不行的。

下面进行设置:

    //设置访问路径
        newCookie.setPath("/ling/");  //  /ling/ 路径下所有子路径都可以访问

先访问 http://localhost:8080/ling/cookies/testCookies 设置Cookie。

image-20200904145515201

再访问 http://localhost:8080/ling/request/getServlet ,验证是否能访问该Cookie。

image-20200904145401787

删除,修改 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想输出在页面,自然也是轮不到他。

image-20200904154635036

这次来真的,删掉 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

image-20200904153737663

可以看到,是可以获取到在硬盘里的 Cookie1 的,输出在页面的还是 Cookie1 ,Cookie2 终究上不了台面,只是 Cookie1 被 Cookie2 覆盖以后,两大高手同归于尽,浏览器再无 Cookie1 Cookie2 。

HTTP协议不仅仅是⽆状态的,⽽且是不安全的!如果不希望Cookie在⾮安全协议中传输,可以设置Cookie的secure属性为true,浏览器只会在HTTPS和SSL等安全协议中传输该Cookie。

//设置只在安全协议下传输 Cookie
        newCookie.setSecure(true);

当然了,设置secure属性不会将Cookie的内容加密。如果想要保证安全,最好使⽤md5算法加密。

显示用户上次访问时间

首先明确需求:

  • 第一次登录,记录下时间;
  • 第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("欢迎您,您是第一次登录。");
    }

第一次登录:

image-20200904205511277

刷新一下:

image-20200904205757353

需要注意的几个点

  • 编码问题:需要设置编码,否则输出的中文会出现乱码。

  • 更新 Cookie 要设置Cookie 的有效期,否则更新的 Cookie 有效期默认为 Session ,关闭浏览器后,Cookie将会被销毁,不会被保存在硬盘。

  • Cookie 不支持特殊符号。下图表示 Cookie 的 Value 中有空格,会报服务器的错。解决方案有两个:

    • 把空格换成别的符号,比如下划线 _ ,横杆 - 等等。
    • 如上代码所示,进行编码,不过注意,取值的时候也要进行解码。

image-20200904195320751

web.xml

参照 HttpServletRespnse 对象相关基本应用 进行配置 Servlet 。

posted @ 2020-09-04 21:18  L1ng14  阅读(418)  评论(1编辑  收藏  举报