session、token與jwt

session、token與jwt

自認爲對session、token與jwt理解還可以,這次來講講這個話題。

session與cookie

什麼是session

session翻譯過來是會話,但在WEB領域常常是指會話數據:“同一客戶端與服務端進行溝通時的上下文信息”,session的作用,我用一個例子來做比喻:

假設我在滴滴平臺上叫車被司機拒載後,撥通了滴滴客服電話進行投訴,通話開始,我是客戶端,滴滴客服則是服務端,我每一次講話就是發送一個請求,她每一次回覆則是響應

以下是一種可能的溝通情形:

我:你好,我在你們平臺上叫了一個車被司機拒載,我現在想投訴他。
客服MM:您好,給您帶來不便非常抱歉,請問您的滴滴賬號是多少?
我:185xxxx8888。
客服MM:您好,請問您要投訴哪一個行程訂單?
我:今天下午3點,從南山星巴克到北山星巴克,司機車牌尾號xxx。

在我與滴滴客服的這次溝通中,session數據就是溝通過程中的信息上下文(滴滴賬號、行程信息、司機車牌…),它的作用不言而喻,沒有這些信息客服就不知道誰因爲什麼在投訴誰,所以session在這的作用是能幫助我完成這個投訴。

通過例子,我們能對session有一個簡單理解,但對於HTTP應該如何記住session的內容卻又是另外一回事,也許這個案例中客服接通我的電話時便撕出了一張便籤紙,便籤紙的頁碼是ignore-sb,隨後過程中我與她溝通的信息她便記錄在這張便籤紙上。

在一對一的客服例子中,客服一直知道是與“我”在對話,但針對HTTP的情況是:“一個客服(服務端)同時處理成百上千的投訴(客戶端)”,客服面臨的挑戰是在串線的情況下,將投訴過程中每一個有效信息記錄到對應的便籤紙上。由於串線,客服無法確定與她溝通的是哪個投訴用戶,也就不能將用戶發言中的有效信息記錄到對應的便籤紙上,在技術上這稱爲HTTP協議的無狀態性:“沒有來電顯示的多路複用通訊”。

針對HTTP通信,如果同一個客戶端先後發出兩個請求,在服務端,如果不借用一些特殊手段是無法得知這兩個請求來自同一個客戶端,這就是HTTP協議的無狀態性,它導致無法追蹤會話,cookie的出現就是爲了解決這個問題。

什麼是cookie

cookie是HTTP協議中定義的一個請求頭部字段,瀏覽器針對這個頭部字段會有一些額外處理邏輯,如自動攜帶、跨域檢查與過期刪除等,與之對應的響應頭部是set-cookie,用於服務器向客戶端寫入cookie數據。

session與cookie的關係

我們需要克服HTTP協議的無狀態性,讓服務端能夠識別同一客戶端,這樣才能記錄並利用session信息,針對上述例子,滴滴公司爲此制訂了一個投訴規則:

投訴用戶如果知道自己的便籤ID,則在每一次發言中必須先說:“我的便籤是xxx”(cookie),然後再講述內容,而客服則判斷用戶有沒有講出自己的便籤ID,有則在已存在中尋找,無則從便籤本中撕取,如果是撕取需要在回答的時候告知(set-cookie)用戶這次撕取便籤的ID,投訴用戶會記住該ID並在後續的每次發言中優先說出:“我的便籤是xxx”

如果大家嚴格遵守該規則,客服針對所有的用戶發言都能夠找到相應的便籤並查看或更新便籤上的信息,這樣客服在串線情況下也能識別用戶(假設串線不會打斷用戶的單次發言)。

在HTTP通信中,客戶端請求在特定位置講出服務端曾經給予自己的某些信息,瀏覽器的這個特定位置就是cookie,這是瀏覽器中最常見的做法。

cookie只是一個特定位置,但session並不依賴這個特定位置,條件允許的情況下我們可以更換成另外一個位置,從而我們知道session並不依賴cookie,如果激進一點,甚至可以說:“session與cookie沒有關係”。

session也許可以脫離cookie存在,但cookie是爲session而生,多年前網景公司爲了解決HTTP無狀態的問題而引進cookie這個HTTP頭部,就是爲了定義瀏覽器行業傳輸session的標準,現在所有的瀏覽器都支持cookie的一系列特性,如自動攜帶、跨域檢查與過期刪除等。不過隨着HTTP應用場景的發展,現今很多HTTP場景也都不再受限於瀏覽器內,所以認識session與cookie的關係就很有必要,當處於在非瀏覽器環境下我們可以選擇擴展HTTP頭部等方式攜帶session信息。

cookie追蹤session的技術實現

這一小節,我用Node.js實現了一個最簡單的cookie/session的example。

session與cookie主要涉及兩個HTTP頭部:set-cookie與cookie,前者是一個響應頭部,用來告知客戶端需要記住的信息,後者是一個請求頭部,用來告知服務端自己曾經記住的信息。

Talk is cheap show you the code:

// index.js
const http = require('http');
const _ = require('lodash');

const session = {};

const server = http.createServer((req, res) => {
    let cookie = {};

    // 從req.headers上獲取cookie並解析爲對象
    _.each((req.headers.cookie || '').split(';'), item => {
        let [name, value] = item.split('=');

        cookie[name] = value;
    });

    // 通過cookie找到session數據,這裏使用's'作爲sessionId的在cookie中的名稱
    if (cookie.s) {
        req.session = session[cookie.s] || {};
    }

    // 用戶首次訪問(沒有sessionId或者session是空)
    // 設置cookie並初始化session
    if (!cookie.s || _.isEmpty(req.session)) {
        req.session = {
            firstVisit: Date.now()
        };

        let sessionId = Math.random().toString(16).substr(2);
        session[sessionId] = req.session;

        // 通過`set-cookie`向客戶端設置cookie
        // 此處沒有考慮多個cookie情況
        res.setHeader('set-cookie', `s=${sessionId}`);
    }

    res.end(`your first visit was ${req.session.firstVisit}.`);
});

server.listen(10000);

通過node index.js運行server,在瀏覽器中訪問http://localhost:10000多次,每次顯示的信息都是相同的,這就是因爲根據cookie識別了同一用戶,取到的session數據是相同的。

上面代碼由於把session放在內存中,故不能支持水平擴展,進程重啓也會導致所有session丟失,解決這兩個問題的一般選擇是將session數據存儲在Redis中。

token

什麼是token

token,翻譯過來是令牌的意思,一般用於請求需要權限校驗的接口,提供一個有效token就允許訪問,否則拒絕訪問。

token,正常是通過身份認證而獲取的一個字符串憑證,這個憑證可用於在一定時間內請求一些權限需要才能獲取的資源,其中最常見的認證方式是用戶名+密碼。

token相比每次認證的優點

因爲認證的方式多種多樣,進行身份驗證後發放token作爲授權,相比每次都進行身份驗證顯得更爲靈活。比如手機號+驗證碼認證與證書認證所需成本相對較大,難以在每一個需要身份校驗的地方都進行這種認證,所以使用token就顯得非常有必要。另外,通過認證發放token的方式能相對減少原始認證要素被盜的機率,同時認證所發放的token一般具有有效期(越敏感有效期越短),也能降低被盜後的可能損失。

token與session及cookie的關係

token之所以會跟session與cookie扯上關係,需要從用戶場景進行解釋,一個web服務提供者面對的用戶通常分爲兩種:遊客與登錄用戶,對於遊客我們一般不存儲session,而登錄用戶至少需要記住其登錄狀態。對於如何記住用戶登錄狀態,常見做法是在用戶登錄時,在其對應session中記錄用戶ID,而對於需要登錄權限的操作,我們可以檢查用戶的session中是否存在用戶ID,否則拒絕操作。

上述這種藉助session識別用戶是否登錄的方式,客戶端cookie中用於追蹤session的那個值(sessionId)可被稱爲token,這個token通常是用戶名+密碼認證成功後所得到的。由此可見session與cookie跟token產生聯繫,是因爲token的概念恰好有一個用session+cookie實現的場景。

token就是token,只是在特定場景利與session、cookie產生了聯繫,因此我們完全可以選擇不借助session與cookie而使用token,比如在HTTP請求頭中使用x-token頭部傳輸token的值,又或者是在URL中攜帶token,這兩者在如今都是較常見的做法。

jwt

什麼是jwt

json形式的網站令牌,英文全稱json web token,其實與token沒多大區別,無非是使用json格式傳輸token罷了。

jwt跟session-token的區別

jwt是token的一種,但它與傳統session-token的核心區別並不是因爲jwt使用json格式,而是在使用方式上有所區別。前面講到token與session及cookie的關係時,sessionId就是token,而sessionId對應的session數據保存在服務器端,一般需要持久化才能滿足應用水平擴展需要。

當用戶量足夠大的時候,考慮到session數據的存儲是單點,可能遭遇瓶頸,於是jwt出現了,它的最大優勢就是可以消滅session存儲這個單點。jwt的具體做法並不複雜,只是把之前存儲在服務端的session數據用json格式轉移到客戶端存儲,同時爲了不被破解和竄改會使用加密和簽名兩個手段。

上述描述過程中我將session與jwt所攜帶的數據等同了,嚴格意義上他們並不等同,因爲jwt攜帶的數據更多的是關注與授權驗證有關的,而所有與會話相關的數據都可以稱爲session,比如我認爲cookie中的所有數據都可以算是session的一部分。

總結

綜合前文,這裏再對token、jwt、session、cookie做一個簡述:

  • token是一個獨立的概念,是通過認證後發放的某個“憑證”,這個憑證可以通過cookie、自定義http頭部,URL的query進行傳遞

  • jwt是token的子集,採用json格式以及附帶一些規範約束

  • session,常常用來表示會話數據,籠統點講就是所有跟會話有關係的數據,具體點就是服務端爲會話所存儲的數據

  • cookie,瀏覽器下最方便傳遞session數據的方式

彩蛋

過了幾天,在app內看到滴滴的投訴處理完成了,故打電話向客服詢問處理結果:

我:你好,我在app內看到上次的投訴處理完成了,請問處理結果是啥?
客服MM:您好,您的投訴我們相關部門已經處理,但是處理結果不對外公佈。
我:處理結果不對外公佈,那我如何知道你們已經處理?
客服MM:先生,您的投訴我們相關部門已經處理,但是無法告知您。
我:…
客服MM:先生,處理結果是我們相關部門已經處理。
我:…
客服MM:先生,您好,我們公司規定處理結果不對外公佈。
我:…
客服MM:先生,真的非常抱歉,我們已經處理,但是處理結果不對外公佈。

這是一個真實的故事,派單給司機時離我大概600米,可能不想跑了就打電話找藉口讓我取消,我沒有答應,隨後,司機把車從我眼前開走並在APP內操作已到達,5分鐘後算我超時被取消,氣炸了後投訴司機,最終也沒能知道有沒有處理以及怎麼處理,這種投訴結果不對外公佈是誰TM想出來的?

博客原文

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章