會話技術之Cookie

凌丹妙藥發表於2020-09-04

會話技術之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 。

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 。

相關文章