如果把人體比作一個web系統的話,cookie、session和token就好像人體的經絡和血管一樣,而web系統中的數據,就好像人體的血液一樣。血液依靠着血管在人體內流動,就如數據根據cookie和session機制在web系統中流動一樣。而web系統開發者就好比一個醫術精湛的醫生,醫生需要十分清楚人體的經絡和血液流向才能對症下藥,而web系統開發者需要十分清楚cookie、session機制,才能迅速解決疑難BUG,開發出更好的web系統。————胡說
引入:
我們都知道http協議本身是一種無狀態的協議,一個普通的請求大致分爲三步:
1、客戶端發送請求給服務器
2、服務器處理該請求
3、服務器將處理結果響應該客戶端。
之後該客戶端再次向該服務區發送請求後,服務器端並不能知道這兩個請求是否是同一個瀏覽器或用戶發出來的。所以作爲web服務器必須能夠採用某種方式來唯一識別同一個用戶,並記錄該用戶的狀態。而這同一個客戶端與服務器端的在一段時間內的多次交互,我們就可以稱該客戶端爲該服務器端的一個客戶端會話窗口,有了會話窗口,我們就能確定哪個請求是哪個用戶發出的了,從而可以實現會話跟蹤,並記錄用戶的行爲。
概念:
會話:可以理解爲用戶打開瀏覽器,訪問該web服務器的多個資源,然後關閉瀏覽器,這中間的一系列過程稱之爲一個會話。
有狀態的會話:瀏覽器發送的每一次請求,每一個會話都要有唯一的標識來唯一標識自己,當瀏覽器發送請求的時候就帶上這個標識來讓服務器識別,從而實現有“狀態”的會話。
javaweb中有兩種實現會話的機制:
1)Cookie機制
2)Session機制
PS:cookie和session是瀏覽器與服務器交互的一種規範,有專門的的組織對該規範進行定義,只要瀏覽器或服務器遵守了該規範,我們就能使用cookie和session。其他能做web開發的高級語言也有,只是實現方式不同罷了。
一、cookie機制
1、基本介紹
1)cookie機制採用的是在客戶端保持 HTTP 狀態信息的方案。當瀏覽器訪問WEB服務器的某個資源時,WEB服務器會在HTTP響應頭中添加一個鍵值對傳送給瀏覽器,再由瀏覽器將該cookie放到客戶端磁盤的一個文件中,該文件可理解爲cookie域(鍵值對的集合),往後每次訪問某個網站時,都會在請求頭中帶着這個網站的所有cookie值。(至於怎麼區分不同網站的cookie的,很簡單,每個網站都給他一個唯一標識比如網址等,每次打開某網址時,就查詢該網站下的所有cookie值即可。)
2)每一個cookie都有一個name和一個value,且name是唯一的。相同名字時,後者會覆蓋掉前者(類似哈希表的key的效果)。
3)一個WEB瀏覽器也可以存儲多個WEB站點提供的Cookie。瀏覽器一般只允許存放300個Cookie,每個站點最多存放20個Cookie,每個Cookie的大小限制爲4KB。
2、分類
1)會話級別的cookie
默認情況下它是一個會話級別的cookie,存儲在瀏覽器的內存中,用戶退出瀏覽器之後被刪除。
2)持久化的cookie
若希望瀏覽器將該cookie存儲在磁盤上,則需要設置該cookie的生命週期setMaxAge,並給出一個以秒爲單位的時間。將最大時效設爲0則是命令瀏覽器刪除該cookie。
3、基本原理
當一個瀏覽器訪問某web服務器時,web服務器會調用HttpServletResponse的addCookie()方法,在響應頭中添加一個名叫Set-Cookie的響應字段用於將Cookie返回給瀏覽器,當瀏覽器第二次訪問該web服務器時會自動的將該cookie回傳給服務器,來實現用戶狀態跟蹤。
4、常用API
javax.servlet.http.Cookie類來封裝Cookie信息,它包含有生成Cookie信息和提取Cookie信息的各個屬性的方法。
1)public Cookie(String name,String value)
2)setMaxAge(int longTime)與getMaxAge方法:設置和獲取cookie的最大有效時長。setMaxAge(0) 表示刪除磁盤上的某個cookie。
注意:
cookie沒有提供修改方法,當name一樣時,覆蓋原來的就算是更新了。
刪除也是,setMaxAge(0),當name一樣時,原來的會被覆蓋掉,新建的沒有生命週期,也會被立馬刪除。
3)setPath與getPath方法 :設置或讀取Cookie的作用範圍。
4)HttpServletResponse接口中定義了一個addCookie(Cookie cookie)方法,它用於在發送給瀏覽器的HTTP響應消息中增加一個Set-Cookie響應頭字段。
5)HttpServletRequest接口中定義了一個getCookies方法,它用於從HTTP請求消息的Cookie請求頭字段中讀取所有的Cookie項。
6)getName方法 :獲取到cookie的name。
7)setValue(String value)與getValue方法:設置和獲取cookie的value。
5、基本應用
自動登錄、跟蹤用戶上次訪問站點的時間、顯示最近瀏覽信息等。
這是我之前寫的一個使用cookie進行自動登錄的服務器代碼,很早了都。
//如果cookie中有customer信息,就放到session中
boolean checkCustomerCookie(HttpServletRequest request) throws UnsupportedEncodingException {
Cookie[] cookies = request.getCookies();
if (cookies != null) {
for (Cookie cookie : cookies) {
String cookieName = cookie.getName();
//如果有,解密後拿cookie中的值和數據庫中的值進行比較
if (Constant.cookieCustomerKey.getName().equals(cookieName)){
String cookieValue = cookie.getValue();
String decry = EncrypUtils.Base64Util.decry(cookieValue);
Customer customer1 = JsonUtils.stringToObject(decry, Customer.class);
Customer customer2 = customerService.checkLogin(customer1.getPhoneNumber(), customer1.getPassword());
if (customer2 != null){
//放入到session中,放行
request.getSession().setAttribute("customer",customer2);
//自動登錄時,更新用戶的在線狀態
Customer onlineCustomer = new Customer();
onlineCustomer.setId(customer2.getId());
onlineCustomer.setOnlineStatus(String.valueOf(Constant.ONLINESTATUS.getCode()));
customerService.updateById(onlineCustomer);
return true;
}
}
}
}
return false;
}
6、Cookie中存儲中文問題
cookie中存儲中文會出現中文亂碼,需要對value進行額外的編碼
1、base64編碼
存儲:Base64.getEncoder().encodeToString(content.getBytes("utf-8"));
讀取:new String(Base64.getDecoder().decode(cookie.getValue()),"utf-8")
2、URLEncoder類
存儲:Cookie cookie = new Cookie("userName", URLEncoder.encode("你好世界", "UTF-8"));
讀取:URLDecoder.decode(cookie.getValue(), "UTF-8")
二、session機制
1、基本介紹
session機制採用的是在服務器端保持 HTTP 狀態信息的方案。爲了加速session的讀取和存儲,web服務器中會開闢一塊內存用來保存服務器端所有的session,每個session都會有一個唯一標識sessionid,根據客戶端傳過來的jsessionid(cookie中),找到對應的服務器端的session。爲了防止服務器端的session過多導致內存溢出,web服務器默認會給每個session設置一個有效期, (30分鐘)若有效期內客戶端沒有訪問過該session,服務器就認爲該客戶端已離線並刪除該session。
保存sessionID的方式
1)cookie中
通過一個特殊的cookie,name爲JSESSIONID,value爲服務器端某個 session的ID,默認的方式。但是當瀏覽器禁用cookie後session就會失效。
2)url重寫
當瀏覽器Cookie被禁時用。
就是把session的id附加在URL路徑的後面。附加的方式也有兩種,一種是作爲URL路徑的附加信息,另一種是作爲查詢字符串附加在URL後面。
做法:
1、response.encodeURL(String url)用於對錶單action和超鏈接的url地址進行重寫
2、response.encodeRedirectURL(String url) 用於對sendRedirect方法後的url地址進行重寫。
這兩個方法很智能,若瀏覽器禁用了cookie,就默認會進行url重寫(url中帶上sessionid),當用戶瀏覽器沒有禁用cookie時,就不在URL後附加sessionid。
用法就是代替response.sendRedirect(String url)。、
2、基本原理
當用戶發送一個請求到服務器端時,服務器會先檢查請求中是否含有sessionid(存在cookie中或者在url中),
》》如果不存在sessionid(說明是第一次請求),就會爲該請求用戶創建一個session對象,並將該session對象的sessionid(放到響應頭的set-cookie中,格式set-cookie:sessionid,下次再請求時cookie中就會有一個name爲jsessionid的cookie,value就是sessionid)響應給客戶端。
》》如果存在sessionid,就會在服務器端查找是否有該sessionid對應的session,如果有就使用,沒有就創建一個。
所以說,服務器端的session和客戶端的cookie是息息相關的,若是沒有了cookie,又不做其他處理的話,服務器端的session也沒了。
3、常用API
-
getId()方法:得到sessionid。
-
invalidate()方法:讓session立刻失效。
-
getAttribute(String key):根據key獲取該session中的value。
-
setAttribute(String key,Object value):往session中存放key-value。
-
removeAttribute(Stringkey):根據key刪除session中的key-value。
-
getServletContext():得到ServletContext。
-
setMaxInactiveInterval(long timeout)/getMaxInactiveInterval:設置/獲取session的最大有效時間。
-
getCreationTime方法:獲取session的創建的時間。
-
getLastAccessedTime方法:獲取session最後一次訪問的時間。
-
getSession():從HttpServletRequest中獲取session。
4、基本應用
跨瀏覽器的會話跟蹤
因爲cookie在多個瀏覽器之間是共享的(但是不能跨域),所以可以將sessionid存在cookie中,再把cookie存入磁盤中,然後在其他瀏覽器中再次訪問該服務器時,就會讀取到cookie中的sessionid,從而回到上次訪問的頁面了。
一段示例代碼:
session.setMaxInactiveInterval(2*3600);//session 保存倆小時
Cookie cookie=new Cookie("JSESSIONID",session.getId());//sessionid放到cookie中
cookie.setMaxAge(2*3600);//客戶端的cookie也保存倆小時
cookie.setPath("/");//cookie作用範圍設爲整個項目
response.addCookie(cookie);//給瀏覽器返回該Cookie
5、常見問題
1、關閉瀏覽器後cookie會消失嗎?
答:看情況。
經過上面關於cookie的分析之後我們知道,cookie默認是存在於瀏覽器內存中的,若此時cookie沒有持久化,瀏覽關閉後cookie會消失;若此時cookie進行了持久化,瀏覽器關閉後cookie不會消失。
2、關閉瀏覽器後session會消失嗎?
答:看起來會實際上不會。
這個問題需要從以下兩個方面考慮:
1)從服務器端考慮
我們知道session是存在於服務器端內存中的,和瀏覽器沒有關係,所以瀏覽器關閉後,服務器端的session不會消失。(除非服務器重啓或session達到了過期時間)
2)從瀏覽器端考慮
我們知道瀏覽器是根據cookie中的jesessionid值來唯一找到服務器端的session的,此時若cookie沒有持久化,瀏覽器關閉後cookie也跟着消失。所以當用戶再次打開瀏覽器後,由於沒有了cookie中的jesessionid,自然也無法唯一找到服務器端的session,對用戶來說,確實是瀏覽器關閉後再次打開就無法找到上次的會話了,誤以爲是關閉瀏覽器後服務器端的session也跟着消失,其實還在。
三、token
1、token是啥?
token,可以翻譯成"令牌",本質上它是一個全局唯一的字符串,用來唯一識別一個客戶端。但它不像cookie和session一樣是一種web規範,個人認爲他是借鑑了cookie和session工作的原理,進而延伸出來的一種維持用戶會話狀態的機制。
2、token解決了什麼問題?
token解決了session依賴於單個Web服務器的問題。單體應用時用戶的會話信息保存在session中,session存在於服務器端的內存中,由於前前後後用戶只針對一個web服務器,所以沒啥問題。但是一到了web服務器集羣的環境下(我們一般都是用Nginx做負載均衡,若是使用了輪詢等這種請求分配策略),就會導致用戶小a在A服務器登錄了,session存在於A服務器中,但是第二次請求被分配到了B服務器,由於B服務器中沒有用戶小a的session會話,導致用戶小a還要再登陸一次,以此類推。這樣用戶體驗很不好。當然解決辦法也有很多種,比如同一個用戶分配到同一個服務處理、使用cookie保持用戶會話信息等。
我們今天討論的是用戶會話信息集中存儲的這種方案。類比之前cookie和session的機制,在請求中根據cookie中的jesessionid來自動找到服務器端的session,從而從session中取出當前用戶的會話信息。
Session session = request.getSession();// 獲取session
session.getAttribute("user") // 獲取session中的用戶信息
3、我們可以模擬cookie和session的這種機制:
①、cookie中是根據jesessionid來找到服務器端的session的,jesessionid就是一個全局唯一的隨機字符串,我們也可以生成一個全局唯一的字符串比如使用UUID或時間戳加隨機字符串的形式;
②、web服務器在內存中存儲所有的session,每個session都有一個唯一的id標識,value就是session本身,session裏面又有好多鍵值對。數據在內存中、key-value形式的、value裏面又有好多鍵值對,這簡直就是對redis的哈希表十分準確的描述啊,所以我們可以使用redis的哈希類型來模型服務器端的session。
③、有了客戶端的隨機字符串,有了服務器端的會話信息存儲,接下來就讓他們匹配起來就完事了,next:
cookie和session機制中是根據cookie中的jesessionid來自動找到服務器端的session,說是自動查找,其實是我們調用了他封裝好的方法request.getSession()獲取的,這個request中就包含了本次請求的所有信息,而調用的getSession()方法中,肯定做了這件事——獲取請求頭中cookie的jesessionid的值,根據這個值到服務器的內存中找到對應的session並返回。所以我們也完全可以模仿它這種機制:我們可以在用戶第一次請求該web服務器時或是用戶登錄該web服務器時,生成一個全局唯一的token返回給前端存儲,同時將該用戶信息存到redis中並設置有效期,之後每次請求中都在請求頭中帶着這個token,服務器端根據這個token到redis中查找對應的用戶信息,即得到了我們所說的 "session"。
客戶端的token我們可以這樣傳:
$.ajax({
headers:{"token":localStorage.getItem('token')},
type: 'get',
url:'/xxx/xxx/xxx',
dataType: 'json',
success: function(we) {
// some code
});
},
服務器端的用戶信息我們可以這樣獲取:
/**
* 返回當前用戶
* @return
*/
public User getCurrUser(){
ServletRequestAttributes servletRequestAttributes =
(ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
String token = servletRequestAttributes.getRequest().getHeader("token");
String strUser = RedisUtils.get(token);
return JsonUtils.stringToObject(strUser,User.class);
}
ok,關於cookie、session和token暫時就這麼多,能看到這兒也不容易,點個贊或評論一下再走,順便給自己也加點經驗值。 這種雙贏的事情以後要多做啊~~~