一. Cookie與Session
1. Cookie與Session
首先, 在學(xué)習(xí)過 HTTP 協(xié)議的基礎(chǔ)上, 我們需要知道 Cookie 是 HTTP 請(qǐng)求報(bào)頭中的一個(gè)關(guān)鍵字段, 本質(zhì)上是瀏覽器在本地存儲(chǔ)數(shù)據(jù)的一種機(jī)制, 要清楚 Cookie 是從哪里來, 要到哪里去.
Cookie
是來自于服務(wù)器的, 通過響應(yīng)報(bào)文中的 Set-Cookie
字段將數(shù)據(jù)返回保存在瀏覽器本地的; 后續(xù)當(dāng)瀏覽器訪問服務(wù)器的時(shí)候, 就會(huì)把本地的 Cookie
通過 HTTP
請(qǐng)求給帶過去.
HTTP 協(xié)議是 “無狀態(tài)” 協(xié)議, 這里的 “無狀態(tài)” 指的是默認(rèn)情況下 HTTP 協(xié)議的客戶端和服務(wù)器之間的每次通信, 和它之前之后的通信是沒有直接的聯(lián)系的, 但是在實(shí)際開發(fā)中多次通信是需要建立起 “上下文” 聯(lián)系的, 用戶發(fā)起請(qǐng)求通過 Cookie 字段將 Cookie 中的內(nèi)容發(fā)送到服務(wù)器, 服務(wù)器就能知道和客戶端的上一次通信處于什么樣的狀態(tài), 此時(shí) Cookie 這種機(jī)制就有了用武之地, 最典型的一種應(yīng)用, 就是使用 Cookie 來標(biāo)識(shí)用戶的身份信息.
常見的網(wǎng)站登錄, 比如淘寶, 我們登錄一次網(wǎng)站后, 后續(xù)再使用訪問淘寶的其他頁面, 是不需要再次登錄的, 還有自動(dòng)登錄功能, 隔了一段時(shí)間再次訪問淘寶網(wǎng)站, 我們會(huì)發(fā)現(xiàn)此時(shí)并不需要再次輸入賬號(hào)密碼登錄, 網(wǎng)站就會(huì)自動(dòng)地幫我們登錄.
為了實(shí)現(xiàn)這種網(wǎng)站自動(dòng)登錄和訪問的功能, 就可以將 Session
和 Cookie
搭配使用, 在用戶在輸入賬號(hào)密碼登錄在淘寶服務(wù)器查詢數(shù)據(jù)庫驗(yàn)證通過后, 服務(wù)器會(huì)創(chuàng)建一個(gè) Session 會(huì)話來保存當(dāng)前用戶的數(shù)據(jù)和信息, 并生成一個(gè) Cookie 數(shù)據(jù).
該 Session 中包含用戶一些關(guān)鍵身份信息, 服務(wù)器會(huì)給這個(gè)用戶分配一個(gè)表示身份的序號(hào), 是具有唯一性的整數(shù)/字符串 (SessionId), 服務(wù)器使用類似于 Hash
表這樣的結(jié)構(gòu)把身份序號(hào) (SessionId
) 作為 Key
, 身份信息 (Session
) 作為 Value
存儲(chǔ)起來, 這樣的每一對(duì)鍵值對(duì)就是一個(gè) Session 會(huì)話.
而服務(wù)器給客戶端返回的 Cookie 里面就包含 SessionId, 瀏覽器就會(huì)在本地將這個(gè) Cookie 儲(chǔ)存起來, 后續(xù)瀏覽器發(fā)送請(qǐng)求的時(shí)候就會(huì)帶上這個(gè) Cookie, 服務(wù)器收到 Cookie 中的身份序號(hào)后, 就會(huì)查詢 Session 會(huì)話表, 如果存在就會(huì)可以正常訪問, 不用重復(fù)的輸入賬號(hào)與密碼, 否則就需要用戶重新輸入賬號(hào)密碼進(jìn)行登錄.
有時(shí)候我們會(huì)發(fā)現(xiàn)登錄網(wǎng)站后隔一段時(shí)間再次登錄, 會(huì)出現(xiàn)讓我們?cè)俅屋斎胭~號(hào)密碼的情況, 此時(shí)就是登錄狀態(tài)失效過期了, 這種情況可能是可能是客戶端把 Cookie 刪了, 也可能是服務(wù)器這把對(duì)應(yīng)的身份信息刪了.
舉個(gè)生活中里例子, 去醫(yī)院看病需要先掛號(hào), 如果你是第一次去這家醫(yī)院的, 就會(huì)給你新辦理一個(gè)就診卡, 這個(gè)就診卡里面就含有你的一些關(guān)鍵身份信息, 并且會(huì)在醫(yī)院的服務(wù)器上新建一個(gè)檔案, 拿著這個(gè)就診卡, 你就可以在該醫(yī)院的各個(gè)科室進(jìn)行刷卡, 如果你之前在這個(gè)醫(yī)院有就診記錄, 你一刷卡就可以查詢到你所有在醫(yī)院的就診信息.
這個(gè)例子中的就診卡就相當(dāng)于是 Cookie, 上面有你最基本的身份信息, 在醫(yī)院服務(wù)器上所儲(chǔ)存有關(guān)你的詳細(xì)信息, 就相當(dāng)于一個(gè) Session 會(huì)話, 服務(wù)器上包含很多用戶的就診信息, 即會(huì)有多個(gè) Session 會(huì)話對(duì)應(yīng)不同的用戶.
??要注意理解 Cookie 和 Session 之間的區(qū)別和關(guān)聯(lián).
- 關(guān)聯(lián): 在網(wǎng)站登錄功能中可以搭配使用.
- 區(qū)別:
- Cookie 是客戶端的存儲(chǔ)機(jī)制, Session 是服務(wù)器的存儲(chǔ)機(jī)制.
- Cookie 里面可以存各種鍵值對(duì) (還可以存除 SessionId 以外的), Session 則專門用來保存用戶信息.
- Cookie 完全可以單獨(dú)使用, 不搭配 Session (實(shí)現(xiàn)非登錄的場(chǎng)景), Session 也可以不搭配 Cookie (手機(jī) App 登錄服務(wù)器, 此時(shí)也需要 Session, 但這里沒有 Cookie 的概念, Cookie 和瀏覽器強(qiáng)相關(guān)的).
- Cookie 是 HTTP 協(xié)議中的一個(gè)部分, Session 則可以和 HTTP 無關(guān) (TCP, WebSocket …也可以用 Session).
2. Servlet會(huì)話管理操作
在 HttpServletRequest
類中, 可以使用 getSession
來獲取或者創(chuàng)建會(huì)話, getCookies
可以獲取請(qǐng)求中的 Cookie 列表.
方法 | 描述 |
---|---|
HttpSession getSession() | 在服務(wù)器中獲取會(huì)話. 參數(shù)如果為 true, 則當(dāng)不存在會(huì)話時(shí)新建會(huì)話; 參數(shù)如果為 false, 則當(dāng)不存在會(huì)話時(shí)返回 null |
Cookie[] getCookies() | 返回一個(gè)數(shù)組, 包含客戶端發(fā)送該請(qǐng)求的所有的 Cookie 對(duì)象. 會(huì)自動(dòng)把 Cookie 中的格式解析成鍵值對(duì). |
調(diào)用 getSession
方法所做的事情:
getSession
有一個(gè) boolean
類型的參數(shù), 如果參數(shù)是 true
, 它有如下行為:
- 讀取
cookie
里的sessionId
字段. - 根據(jù)
sessionId
來查詢對(duì)應(yīng)的HttpSession
對(duì)象在服務(wù)器上是否存在. - 如果不存在, 就創(chuàng)建一個(gè)新的會(huì)話, 即創(chuàng)建一個(gè)新的
HttpSession
對(duì)象, 并生成一個(gè)唯一的sessionId
, 會(huì)以新生成的sessionId
作為Key
, 生成的HttpSession
對(duì)象作為Value
, 以鍵值對(duì)形式儲(chǔ)存到類似于Hash
的結(jié)構(gòu)中, 然后將sessionId
設(shè)置到響應(yīng)報(bào)文中的set-Cookie
字段返回給瀏覽器. - 如果存在就直接返回查詢到的
HttpSession
對(duì)象.
如果參數(shù)是 false
, 行為如下:
- 讀取
cookie
里的sessionId
字段. - 根據(jù)
sessionId
來查詢對(duì)應(yīng)的HttpSession
對(duì)象在服務(wù)器上是否存在. - 如果不存在, 直接返回
null
. - 如果存在就直接返回查詢到的
HttpSession
對(duì)象.
總之就是, getSession
的參數(shù)為 true
時(shí)允許創(chuàng)建 Session
會(huì)話, 為 false
時(shí)不允許創(chuàng)建 Session
會(huì)話.
??關(guān)于HttpSession
這個(gè)對(duì)象也可以看作是一個(gè)哈希表, 是以鍵值對(duì)的形式存儲(chǔ)數(shù)據(jù)的, 并且允許程序員在對(duì)象中儲(chǔ)存任意的鍵值對(duì)數(shù)據(jù), 但是 Key
必須是 String
類型, Value
的類型是 Object
, 設(shè)置就比較隨意了.
該類中提供了兩個(gè)方法可以用來獲取該對(duì)象中鍵值和設(shè)置鍵值對(duì).
方法 | 描述 |
---|---|
Object getAttribute(String name) | 查詢 session 會(huì)話中指定鍵的鍵值, 查不到則返回 null. |
void setAttribute(String name, Object value) | 綁定一個(gè)鍵和值到該 session 會(huì)話中 |
boolean isNew() | 判定當(dāng)前是否是新創(chuàng)建出的會(huì)話 |
所以服務(wù)器組織會(huì)話的方式就如下圖:
二. 登錄邏輯的實(shí)現(xiàn)
我們這里模擬實(shí)現(xiàn)一個(gè)登錄的功能, 很多網(wǎng)站都會(huì)讓你先登錄, 才能使用其中的一些功能, 我們這里實(shí)現(xiàn)登錄完成之后, 就跳到另一個(gè)主頁, 不進(jìn)行登錄的話, 這個(gè)主頁是不能被訪問的.
所以, 我們這里主要涉及到兩個(gè)頁面, 第一個(gè)是登錄頁面, 第二個(gè)是登錄成功后要跳轉(zhuǎn)的主頁面.
登錄頁面包含兩個(gè)輸入框 (用來輸入用戶名和密碼) 和一個(gè)登錄按鈕, 點(diǎn)擊登錄按鈕就會(huì)發(fā)起一個(gè) HTTP 請(qǐng)求, 服務(wù)器處理這個(gè)請(qǐng)求的時(shí)候就會(huì)驗(yàn)證用戶名和密碼, 驗(yàn)證通過就會(huì)跳轉(zhuǎn)到主頁, 主頁就簡單的顯示出當(dāng)前用戶的用戶名就行了.
實(shí)現(xiàn)整這里的邏輯就涉及到兩個(gè) Servlet 類:
- 處理登錄的
LoginServlet
, 用來判段用戶名和密碼, 登錄成功和登錄失敗的情況. - 構(gòu)造主頁面的
IndexServlet
, 用來構(gòu)造登錄成功的主頁.
1??第一步, 約定前后端接口.
我們需要實(shí)現(xiàn)兩套交互邏輯, 一是登錄跳轉(zhuǎn), 二是獲取主頁.
登錄跳轉(zhuǎn), 這里只是示范作用, 約定只有一個(gè)用戶, 就不加數(shù)據(jù)庫的邏輯了.
約定用戶名就是 zhangsan
, 密碼就是 123
, 使用 POST
請(qǐng)求, 響應(yīng)采用 302
重定向.
獲取主頁, 采用 GET
請(qǐng)求, 響應(yīng)返回一個(gè)頁面.
2??第二步, 編寫前端交互頁面
目標(biāo)頁面如下:
這里的場(chǎng)景很簡單. 就直接使用 form
表單構(gòu)造來 Post
請(qǐng)求了.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>login</title>
</head>
<body>
<form action="login" method="post">
<span>用戶名</span>
<input type="text" name="username">
<span>密碼</span>
<input type="password" name="password">
<input type="submit" value="登錄">
</form>
</body>
</html>
3??第三步, 編寫后端處理代碼
對(duì)于登錄跳轉(zhuǎn)主頁的 Post 請(qǐng)求處理思路如下:
- 從請(qǐng)求中獲取用戶名和密碼.
- 驗(yàn)證用戶名和密碼是否正確, 正常來說是要查詢數(shù)據(jù)庫的, 這里就不添加數(shù)據(jù)庫的邏輯了.
- 如果驗(yàn)證通過, 創(chuàng)建會(huì)話, 并將
username
和主頁被訪問的次數(shù)數(shù)據(jù)添加到會(huì)話中 (保存必要的用戶信息), 創(chuàng)建好會(huì)話后, 重定向到主頁index
. - 如果驗(yàn)證不通過, 重定向到登錄頁面.
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
@WebServlet("/login")
public class LoginServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("utf8");
resp.setCharacterEncoding("utf8");
// 獲取用戶名和密碼
String username = req.getParameter("username");
String password = req.getParameter("password");
// 驗(yàn)證用戶名密碼是否正確
// 這里約定合法的用戶名為zhangsan,密碼是123
if (username.equals("zhangsan") && password.equals("123")) {
// 登陸成功!!
// 創(chuàng)建一個(gè)會(huì)話
HttpSession session = req.getSession(true);
// 把當(dāng)前的用戶名保存到會(huì)話中
session.setAttribute("username", username);
// 設(shè)置初始情況下登錄成功訪問主頁的次數(shù)為0
session.setAttribute("count", 0);
// 重定向到主頁
resp.sendRedirect("index");
} else {
// 登陸失敗!!
// 重定向到 登陸頁面
System.out.println("用戶名或者密碼錯(cuò)誤!");
resp.sendRedirect("login.html");
}
}
}
獲取主頁的 GET 請(qǐng)求處理思路:
- 獲取會(huì)話 (請(qǐng)求中包含
sessionId
, 會(huì)話是根據(jù)sessionId
獲取的) - 取出會(huì)話信息, 將主頁返回次數(shù)加
1
并寫回到會(huì)話信息中. - 返回一個(gè)簡單的頁面, 顯示用戶名和主頁訪問次數(shù).
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
@WebServlet("/index")
public class IndexServlet extends HttpServlet {
// 重定向, 瀏覽器發(fā)起的是GET請(qǐng)求
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 先判定用戶的登陸狀態(tài).
// 如果用戶還沒登陸, 要求先登陸.
// 已經(jīng)登陸了, 則根據(jù)會(huì)話中的用戶名, 將相關(guān)信息顯示到頁面上.
HttpSession session = req.getSession(false);
if (session == null) {
// 未登錄狀態(tài)
System.out.println("用戶未登錄!");
// 重定向到登錄頁面
resp.sendRedirect("login.html");
return;
}
// 已經(jīng)登陸, 取出會(huì)話信息
String username = (String)session.getAttribute("username");
Integer count = (Integer)session.getAttribute("count");
// 訪問次數(shù)加 1 后再寫回到會(huì)話中
count++;
session.setAttribute("count", count);
// 構(gòu)造頁面
resp.setContentType("text/html;charset=utf8");
resp.getWriter().write("<h3>歡迎您!" + username + "</h3> <h4>這個(gè)主頁已經(jīng)被訪問了" + count + "次</h4>");
}
}
抓包分析交互過程:
第一次交互, 瀏覽器從服務(wù)器獲取登錄頁面.
第二次交互, 瀏覽器給服務(wù)器一個(gè)登錄請(qǐng)求, 服務(wù)器返回響應(yīng), 重定向頁面.
第三次交互, 瀏覽器收到 302
重定向響應(yīng)后, 再次向服務(wù)器發(fā)起請(qǐng)求, 訪問主頁.
響應(yīng)結(jié)果:
用戶名或者密碼錯(cuò)誤的情況下:
未登錄直接訪問的情況下:
這里關(guān)于 IDEA
集成的 Tomcat
環(huán)境有一些需要注意的點(diǎn), 正常來說, 上面說的 sessionId
并不會(huì)一直存在下去, 比如 Tomcat
服務(wù)器重新啟動(dòng)的時(shí)候, 原來服務(wù)器在內(nèi)存中維護(hù)的會(huì)話 Hash
表就應(yīng)該沒有了, 此時(shí)再次訪問, 就應(yīng)該出現(xiàn) sessionld
查不到, 就被識(shí)別成 “未登錄” 狀態(tài)了, 但是有些版本 Smart Tomcat
為了方便程序猿調(diào)試程序, 會(huì)在停止服務(wù)器的時(shí)候把會(huì)話持久化保存, 并且在下次啟動(dòng)的時(shí)候自動(dòng)把會(huì)話恢復(fù)到內(nèi)存中, 在這種情況下會(huì)話是不丟失的.文章來源:http://www.zghlxwxcb.cn/news/detail-479001.html
但如果是手動(dòng)部署程序到 Tomcat
, 則會(huì)話默認(rèn)還是在內(nèi)存中, 重啟服務(wù)器是會(huì)丟失的.文章來源地址http://www.zghlxwxcb.cn/news/detail-479001.html
到了這里,關(guān)于從Cookie到Session: Servlet API中的會(huì)話管理詳解的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!