什麼是cookie,什麼是session

SAPmatinal發表於2019-01-15

更多內容關注公眾號:SAP Technical 

cookie產生背景

由於HTTP協議是無狀態的,而伺服器端的業務必須是要有狀態的。Cookie誕生的最初目的是為了儲存web中的狀態資訊,以方便伺服器端使用。比如判斷使用者是否是第一次訪問網站

cookie

  1. Cookie是客戶端技術,程式把每個使用者的資料以cookie的形式寫給使用者各自的瀏覽器。當使用者使用瀏覽器再去訪問伺服器中的web資源時,就會帶著各自的資料去。這樣,web資源處理的就是使用者各自的資料了。
  2. cookie的處理:
  • 伺服器像客戶端傳送cookie
  • 瀏覽器將cookie儲存
  • 之後每次http請求瀏覽器都會將cookie傳送給伺服器端
  1. Java提供的操作Cookie的API

Java中的javax.servlet.http.Cookie類用於建立一個Cookie

方法 型別 描述
Cookie(String name,String value) 構造方法 例項化Cookie物件,傳入cooke名稱和cookie的值
public String getName() 普通方法 取得Cookie的名字
public String getValue() 普通方法 取得Cookie的值
public void setValue(String newvalue) 普通方法 設定Cookie的值
public void setMaxAge(int expiry) 普通方法 設定Cookie的最大儲存時間,即cookie的有效期,當伺服器給瀏覽器回送一個cookie時,如果在伺服器端沒有呼叫setMaxAge方法設定cookie的有效期,那麼cookie的有效期只在一次會話過程中有效,使用者開一個瀏覽器,點選多個超連結,訪問伺服器多個web資源,然後關閉瀏覽器,整個過程稱之為一次會話,當使用者關閉瀏覽器,會話就結束了,此時cookie就會失效,如果在伺服器端使用setMaxAge方法設定了cookie的有效期,比如設定了30分鐘,那麼當伺服器把cookie傳送給瀏覽器時,此時cookie就會在客戶端的硬碟上儲存30分鐘,在30分鐘內,即使瀏覽器關了,cookie依然存在,在30分鐘內,開啟瀏覽器訪問伺服器時,瀏覽器都會把cookie一起帶上,這樣就可以在伺服器端獲取到客戶端瀏覽器傳遞過來的cookie裡面的資訊了,這就是cookie設定maxAge和不設定maxAge的區別,不設定maxAge,那麼cookie就只在一次會話中有效,一旦使用者關閉了瀏覽器,那麼cookie就沒有了,那麼瀏覽器是怎麼做到這一點的呢,我們啟動一個瀏覽器,就相當於啟動一個應用程式,而伺服器回送的cookie首先是存在瀏覽器的快取中的,當瀏覽器關閉時,瀏覽器的快取自然就沒有了,所以儲存在快取中的cookie自然就被清掉了,而如果設定了cookie的有效期,那麼瀏覽器在關閉時,就會把快取中的cookie寫到硬碟上儲存起來,這樣cookie就能夠一直存在了。
public int getMaxAge() 普通方法 獲取Cookies的有效期
public void setPath(String Url) 普通方法 設定cookie的有效路徑,比如把cookie的有效路徑設定為"/xdp",那麼瀏覽器訪問"xdp"目錄下的web資源時,都會帶上cookie,再比如把cookie的有效路徑設定為"/xdp/gacl",那麼瀏覽器只有在訪問"xdp"目錄下的"gacl"這個目錄裡面的web資源時才會帶上cookie一起訪問,而當訪問"xdp"目錄下的web資源時,瀏覽器是不帶cookie的
public String getPath() 普通方法 獲取cookie的有效路徑
public void setDomain(String pattern) 普通方法 設定cookie的有效域
public String getDomain() 普通方法 獲取cookie的有效域

response介面也中定義了一個addCookie方法,它用於在其響應頭中增加一個相應的Set-Cookie頭欄位。 同樣,request介面中也定義了一個getCookies方法,它用於獲取客戶端提交的Cookie。

package gac.xdp.cookie;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.Date;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * @author gacl
 * cookie例項:獲取使用者上一次訪問的時間
 */
public class CookieDemo01 extends HttpServlet {

    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        //設定伺服器端以UTF-8編碼進行輸出
        response.setCharacterEncoding("UTF-8");
        //設定瀏覽器以UTF-8編碼進行接收,解決中文亂碼問題
        response.setContentType("text/html;charset=UTF-8");
        PrintWriter out = response.getWriter();
        //獲取瀏覽器訪問訪問伺服器時傳遞過來的cookie陣列
        Cookie[] cookies = request.getCookies();
        //如果使用者是第一次訪問,那麼得到的cookies將是null
        if (cookies!=null) {
            out.write("您上次訪問的時間是:");
            for (int i = 0; i < cookies.length; i++) {
                Cookie cookie = cookies[i];
                if (cookie.getName().equals("lastAccessTime")) {
                    Long lastAccessTime =Long.parseLong(cookie.getValue());
                    Date date = new Date(lastAccessTime);
                    out.write(date.toLocaleString());
                }
            }
        }else {
            out.write("這是您第一次訪問本站!");
        }
        
        //使用者訪問過之後重新設定使用者的訪問時間,儲存到cookie中,然後傳送到客戶端瀏覽器
        Cookie cookie = new Cookie("lastAccessTime", System.currentTimeMillis()+"");//建立一個cookie,cookie的名字是lastAccessTime
        //將cookie物件新增到response物件中,這樣伺服器在輸出response物件中的內容時就會把cookie也輸出到客戶端瀏覽器
        response.addCookie(cookie);
    }

    public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        doGet(request, response);
    }

}

session

  1. session的處理:
  2. 瀏覽器第一次訪問伺服器,伺服器會建立一個session,並生成一個sessionId
  3. 將sessionid及對應的session分別作為key和value儲存到快取中,也可以持久化到資料庫中
  4. 伺服器再把sessionid,以cookie的形式傳送給客戶端
  5. 瀏覽器下次再訪問時,會直接帶著cookie中的sessionid。然後伺服器根據sessionid找到對應的session進行匹配;
  6. session常用方法
public void setAttribute(String name,String value)設定指定名字的屬性的值,並將它新增到session會話範圍內,如果這個屬性是會話範圍記憶體在,則更改該屬性的值。
  
public Object getAttribute(String name)在會話範圍內獲取指定名字的屬性的值,返回值型別為object,如果該屬性不存在,則返回null。
  
public void removeAttribute(String name),刪除指定名字的session屬性,若該屬性不存在,則出現異常。  

public void invalidate(),使session失效。可以立即使當前會話失效,原來會話中儲存的所有物件都不能再被訪問。  

public String getId( ),獲取當前的會話ID。每個會話在伺服器端都存在一個唯一的標示sessionID,session物件傳送到瀏覽器的唯一資料就是sessionID,它一般儲存在cookie中。  

public void setMaxInactiveInterval(int interval) 設定會話的最大持續時間,單位是秒,負數表明會話永不失效。  

public int getMaxInActiveInterval(),獲取會話的最大持續時間。  

使用session物件的getCreationTime()和getLastAccessedTime()方法可以獲取會話建立的時間和最後訪問的時間,但其返回值是毫秒,一般需要使用下面的轉換來獲取具體日期和時間。  

3.基於session的使用者認證

 

 

 

 

token(訪問資源的令牌)

1.token處理流程:

  1. 把使用者的使用者名稱和密碼發到後端
  2. 後端進行校驗,校驗成功會生成token, 把token傳送給客戶端
  3. 客戶端自己儲存token, 再次請求就要在Http協議的請求頭中帶著token去訪問服務端,和在服務端儲存的token資訊進行比對校驗。

     

     

JWT(json web token)

組成:
一個jwt實際上就是一個字串,它由三部分組成,頭部、載荷與簽名,這三個部分都是json格式。
一、頭部(Header)
頭部用於描述關於該JWT的最基本的資訊,例如其型別以及簽名所用的演算法等。

{
  "typ": "JWT",
  "alg": "HS256"
}

在這裡,我們說明了這是一個JWT,並且我們所用的簽名演算法是HS256演算法。
二、載荷(Payload)

{
    "iss": "John Wu JWT",
    "iat": 1441593502,
    "exp": 1441594722,
    "aud": "www.example.com",
    "sub": "jrocket@example.com",
    "from_user": "B",
    "target_user": "A"
}

驗證流程:

  1. 在頭部資訊中宣告加密演算法和常量, 然後把header使用json轉化為字串
  2. 在載荷中宣告使用者資訊,同時還有一些其他的內容;再次使用json 把載荷部分進行轉化,轉化為字串
  3. 使用在header中宣告的加密演算法和每個專案隨機生成的secret來進行加密, 把第一步分字串和第二部分的字串進行加密, 生成新的字串。詞字串是獨一無二的。
  4. 解密的時候,只要客戶端帶著JWT來發起請求,服務端就直接使用secret進行解密。
    這裡面的前五個欄位都是由JWT的標準所定義的。
  • iss: 該JWT的簽發者
  • sub: 該JWT所面向的使用者
  • aud: 接收該JWT的一方
  • exp(expires): 什麼時候過期,這裡是一個Unix時間戳
  • iat(issued at): 在什麼時候簽發的
    把頭部和載荷分別進行Base64編碼之後得到兩個字串,然後再將這兩個編碼後的字串用英文句號.連線在一起(頭部在前),形成新的字串:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJmcm9tX3VzZXIiOiJCIiwidGFyZ2V0X3VzZXIiOiJBIn0

三、簽名(signature)
最後,我們將上面拼接完的字串用HS256演算法進行加密。在加密的時候,我們還需要提供一個金鑰(secret)。加密後的內容也是一個字串,最後這個字串就是簽名,把這個簽名拼接在剛才的字串後面就能得到完整的jwt。header部分和payload部分如果被篡改,由於篡改者不知道金鑰是什麼,也無法生成新的signature部分,服務端也就無法通過,在jwt中,訊息體是透明的,使用簽名可以保證訊息不被篡改。
特點:

  1. 三部分組成,每一部分都進行字串的轉化
  2. 解密的時候沒有使用資料庫,僅僅使用的是secret進行解密。
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJmcm9tX3VzZXIiOiJCIiwidGFyZ2V0X3VzZXIiOiJBIn0.rSWamyAYwuHCo7IFAgd1oRpSP7nzL7BF5t7ItqpKViM

和session的區別:

基於session和基於jwt的方式的主要區別就是使用者的狀態儲存的位置,session是儲存在服務端的,而jwt是儲存在客戶端的。

  • 應用程式分散式部署的情況下,session需要做多機資料共享,通常可以存在資料庫或者redis裡面。而jwt不需要。
  • jwt不在服務端儲存任何狀態。RESTful API的原則之一是無狀態,發出請求時,總會返回帶有引數的響應,不會產生附加影響。使用者的認證狀態引入這種附加影響,這破壞了這一原則。另外jwt的載荷中可以儲存一些常用資訊,用於交換資訊,有效地使用 JWT,可以降低伺服器查詢資料庫的次數。

jwt的缺點:

  1. 安全性
    由於jwt的payload是使用base64編碼的,並沒有加密,因此jwt中不能儲存敏感資料。而session的資訊是存在服務端的,相對來說更安全。
  2. 效能
    jwt太長。由於是無狀態使用JWT,所有的資料都被放到JWT裡,如果還要進行一些資料交換,那載荷會更大,經過編碼之後導致jwt非常長,cookie的限制大小一般是4k,cookie很可能放不下,所以jwt一般放在local storage裡面。並且使用者在系統中的每一次http請求都會把jwt攜帶在Header裡面,http請求的Header可能比Body還要大。而sessionId只是很短的一個字串,因此使用jwt的http請求比使用session的開銷大得多。
  3. 一次性
    無狀態是jwt的特點,但也導致了這個問題,jwt是一次性的。想修改裡面的內容,就必須簽發一個新的jwt。
    (1)無法廢棄
    通過上面jwt的驗證機制可以看出來,一旦簽發一個jwt,在到期之前就會始終有效,無法中途廢棄。例如你在payload中儲存了一些資訊,當資訊需要更新時,則重新簽發一個jwt,但是由於舊的jwt還沒過期,拿著這個舊的jwt依舊可以登入,那登入後服務端從jwt中拿到的資訊就是過時的。為了解決這個問題,我們就需要在服務端部署額外的邏輯,例如設定一個黑名單,一旦簽發了新的jwt,那麼舊的就加入黑名單(比如存到redis裡面),避免被再次使用。
    (2)續簽
    如果你使用jwt做會話管理,傳統的cookie續簽方案一般都是框架自帶的,session有效期30分鐘,30分鐘內如果有訪問,有效期被重新整理至30分鐘。一樣的道理,要改變jwt的有效時間,就要簽發新的jwt。最簡單的一種方式是每次請求重新整理jwt,即每個http請求都返回一個新的jwt。這個方法不僅暴力不優雅,而且每次請求都要做jwt的加密解密,會帶來效能問題。另一種方法是在redis中單獨為每個jwt設定過期時間,每次訪問時重新整理jwt的過期時間。
    可以看出想要破解jwt一次性的特性,就需要在服務端儲存jwt的狀態。但是引入 redis 之後,就把無狀態的jwt硬生生變成了有狀態了,違背了jwt的初衷。而且這個方案和session都差不多了。

基於token的認證方案

 

相關文章