1. 程式人生 > >用戶認證和授權

用戶認證和授權

csr 接收 也會 指定 才會 接口 截取 swa 認證服務器

概述

因為做的項目涉及到用戶認證和授權,所以好好總結了一下這塊。

認證和授權

一般我們說的認證主要指的是用戶登錄認證;一般我們說的授權主要是第三方授權

用戶登錄認證主要有2種方法,一種是基於session-id的認證,另一種是基於token的認證。

第三方授權主要是Oauth2.0標準的授權,它主要包括授權碼模式的授權和簡單模式的授權。

基於session-id的認證

基於session-id的認證是比較傳統的認證方式,它包含以下幾個步驟

  1. 用戶輸入用戶名和密碼,發送給服務器。
  2. 服務器驗證這個用戶名和密碼,如果成功就生成一個會話id(session-id),然後把這個會話id同時保存在服務器和瀏覽器的cookie裏面。因為保存的地方是瀏覽器的cookie,所以這種認證方式也叫基於cookie的認證方式。
  3. 用戶每次請求資源,都會把這個會話id發送給服務器,服務器端就在本地找這個會話id,如果能夠找到,就返回用戶請求的數據。
  4. 當用戶退出登錄時,會話同時在客戶端和服務器端被銷毀。

對於這種方式,我們前端的處理方式是:

  1. 發送用戶名和密碼給服務器,並接收服務器返回的會話id。
  2. 服務器會自動把會話id儲存在瀏覽器的cookie裏面,不需要我們前端處理。
  3. 由於在發送http請求的時候會自動帶上cookie裏面的數據,所以在發送其它http請求的時候不需要前端特殊處理。
  4. 用戶登出的時候(註意是用戶主動註銷或者退出登錄),前端需要請求一個登出接口,然後服務器會銷毀儲存在瀏覽器cookie中的會話id。

需要註意的是,上面所有的一切都由服務器來完成,前端只請求接口就行了。服務器能夠自己把會話id寫入到瀏覽器的cookie裏面。

基於token的認證

基於token的認證主要是指json web token認證,即jwt認證。

它主要包含如下幾個步驟

  1. 用戶輸入用戶名和密碼,發送給服務器。
  2. 服務器驗證用戶名和密碼,成功則返回一個token(可以認為是一個很長的字符串)給瀏覽器。
  3. 後續每次請求,瀏覽器都會把token發送給服務器,服務器驗證token是否有效,有效則返回用戶請求的數據。
  4. 當用戶退出登錄時,瀏覽器端銷毀這個token就行了。

需要註意的是,jwt認證有一個很重要的特點:服務器端沒有儲存token

為什麽服務器端不需要儲存token呢,我們來看一下token的生成過程。

jwt這個字符串是由三部分組成:header(頭部),payload(主體信息),signature(簽名)。

頭部是給JWT的基本信息,然後通過加密(base64加密)得到的。通過解密可以得到原始信息。比如下面這段基本信息:類型是jwt,簽名算法是HS256。

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

加密後的頭部如下:

ewogICJ0eXAiOiAiSldUIiwKICAiYWxnIjogIkhTMjU2Igp9

主體信息是描述用戶的信息,然後通過加密(base64加密)得到的。通過解密可以得到原始信息。比如下面這段主體信息。

{
    "iss": "Yang JWT",
    "iat": 1441593502,
    "exp": 1441594722,
    "aud": "www.example.com",
    "sub": "[email protected]",
    "from_user": "B",
    "target_user": "A"
}

加密後的主體信息如下:

ew0KICAgICJpc3MiOiAiWWFuZyBKV1QiLA0KICAgICJpYXQiOiAxNDQxNTkzNTAyLA0KICAgICJleHAiOiAxNDQxNTk0NzIyLA0KICAgICJhdWQiOiAid3d3LmV4YW1wbGUuY29tIiwNCiAgICAic3ViIjogInlhbmdAZXhhbXBsZS5jb20iLA0KICAgICJmcm9tX3VzZXIiOiAiQiIsDQogICAgInRhcmdldF91c2VyIjogIkEiDQp9

將上面加密後的2段字符串用點號連接在一起,如下所示:

ewogICJ0eXAiOiAiSldUIiwKICAiYWxnIjogIkhTMjU2Igp9.ew0KICAgICJpc3MiOiAiWWFuZyBKV1QiLA0KICAgICJpYXQiOiAxNDQxNTkzNTAyLA0KICAgICJleHAiOiAxNDQxNTk0NzIyLA0KICAgICJhdWQiOiAid3d3LmV4YW1wbGUuY29tIiwNCiAgICAic3ViIjogInlhbmdAZXhhbXBsZS5jb20iLA0KICAgICJmcm9tX3VzZXIiOiAiQiIsDQogICAgInRhcmdldF91c2VyIjogIkEiDQp9

再對上面的字符串進行HS256算法加密,加密的時候需要我們提供一個密鑰(比如我們設置密鑰為63161014009),加密後的內容就是簽名:

rSWamyAYwuHCo7IFAgd1oRpSP7nzL7BF5t7ItqpKViM

最後再把簽名通過點號拼在最後就得到了token:

ewogICJ0eXAiOiAiSldUIiwKICAiYWxnIjogIkhTMjU2Igp9.ew0KICAgICJpc3MiOiAiWWFuZyBKV1QiLA0KICAgICJpYXQiOiAxNDQxNTkzNTAyLA0KICAgICJleHAiOiAxNDQxNTk0NzIyLA0KICAgICJhdWQiOiAid3d3LmV4YW1wbGUuY29tIiwNCiAgICAic3ViIjogInlhbmdAZXhhbXBsZS5jb20iLA0KICAgICJmcm9tX3VzZXIiOiAiQiIsDQogICAgInRhcmdldF91c2VyIjogIkEiDQp9.rSWamyAYwuHCo7IFAgd1oRpSP7nzL7BF5t7ItqpKViM

上面的三個加密都是可逆的,其中頭部和主體信息任何人都能用base64解密,簽名需要密鑰才能解密。黑客沒有密鑰是不能偽造簽名的,所以也不能偽造token。然後,在服務器端只需要重新對token裏的頭部和主體信息用密鑰加密一次,然後和簽名對比,就能知道這個token合不合法了。所以根本就不需要把token存在本地。

由於服務器不需要把token存在本地,節省了查找token的時間,在用戶登出的時候,服務器也不需要銷毀token等額外處理。所以jwt減少了服務器的很多壓力。

值得一提的是,上面只是說明了一種jwt的模式,過程中都可以根據實際情況對流程進行改造。比如有的業務把token放到http header裏面發送給服務器,有的業務把token放到url參數裏面發送給服務器。

雖然jwt相比session-id有很多優勢,但是它不能解決jwt失效的問題,除非設置過期時間。也就是說,如果把token放在服務器中儲存的話,能夠解決這個問題:就是假如我儲存token在redis裏面,每次認證的時候比較這個token,這樣就能做到,用戶A用A的信息登錄之後,用戶B再用A的信息登錄,那麽用戶A的登錄就會失效,因為儲存了新的token(用戶B收到的token)。但是jwt實現不了這種情形。

所以說,jwt只適用於有效期極短的token!!!它並不能取代session-id!!!

混合認證

由於基於session-id的認證有如下缺點:

  1. 全部靠服務端完成,服務端擁有狀態,壓力會很大。
  2. 對於跨域請求,不會自動發送cookie。

基於jwt的認證又有如下缺點:

  1. 只能通過設置有效期來廢除token,如果token的有效期設置太長,那麽在這段時間內沒有辦法廢除這個token。

所以對於一般的認證,現在通常用混合方法,具體如下:

  1. 用戶輸入用戶名和密碼,發送給服務器。
  2. 服務器驗證用戶名和密碼,成功則返回一個token給瀏覽器,並且把這個token和用戶名一起儲存在服務器中。
  3. 後續每次請求,瀏覽器都會把token發送給服務器,服務器把收到的token和本地的對比,一致則返回用戶請求的數據。
  4. 每次用戶重新登錄,服務器都會重新生成一個token,並清除以前的token,儲存起來。
  5. 當用戶退出登錄時,瀏覽器端銷毀這個token,服務器不作處理。

這種認證的優點是:

  1. 服務端只需要生成和儲存token,壓力會小很多,考慮到存取速度,可以用redis來儲存token,這樣讀取和查找會很快。
  2. 服務端沒有狀態,也適合restful api。

授權碼模式的授權

授權碼模式是Oauth2.0中最精彩的授權模式。它的步驟如下:

比如說,用戶需要在豆瓣上註冊登錄,希望用用戶的qq的信息來註冊,這個時候就需要取得第三方QQ的授權,並拿取QQ裏面這個用戶的信息。

  1. 用戶訪問豆瓣,豆瓣將用戶導向QQ的認證服務器頁面。
  2. 用戶選擇同意QQ的授權。
  3. QQ的認證服務器頁面將用戶導向豆瓣指定的“重定向URI”,並且附上一個授權碼(code)。
  4. 豆瓣指定的“重定向URI”利用授權code,再加上儲存在“重定向URI”裏面的服務器id和密碼,向QQ的認證服務器申請令牌(token)。
  5. QQ的認證服務器核對授權code,服務器id和密碼,確認無誤之後,向“重定向URI”發送令牌(token)。
  6. “重定向URI”利用令牌(token)向QQ的資源服務器申請用戶信息,然後在豆瓣註冊此用戶。
  7. 用戶在豆瓣的註冊完成。(不需要用戶填寫任何用戶信息)

有下面幾點需要註意:

  1. 黑客即使獲取了第二步中的授權碼也沒用,因為黑客沒有服務器id和密碼,所以黑客還是不能訪問QQ的服務器中的用戶信息。

  2. 服務器id和密碼是什麽?服務器id和密碼是豆瓣的“重定向URI”向QQ的認證服務器註冊的時候獲得的id和密碼,作為豆瓣的標識。在註冊的時候,QQ的認證服務器也會儲存這個“重定向URI”,所以不同的用戶都會導向同一個“重定向URI”。

  3. 第4步中的請求必須用https請求,否則授權code,服務器id和密碼有可能被黑客截獲,認證服務器發送回的令牌也可能被截獲。

  4. 為什麽需要一個“重定向URI”?首先,豆瓣的服務器id和密碼不能儲存在其它頁面,它只能儲存在豆瓣的“重定向URI”裏面,否則很可能被泄漏。其次,“重定向URI”和認證服務器的通信必須為https,一般的豆瓣頁面與認證服務器的通信可能不是https。

  5. 在第6步中,“重定向URI”可能會把令牌(token)發回給豆瓣原頁面,也可能制造一個豆瓣的token發回給原頁面。

  6. Oauth2.0還有一個refresh_token。一般QQ的認證服務器發回的token的有效期很短,而refresh_token的有效期比較長,所以當token過期的時候,可以通過refresh_token向QQ的認證服務器申請token。

  7. 用戶一共要進行3次uri跳轉,一次是跳轉到QQ的認證服務器。第二次是跳轉到“重定向URI”。第三次跳轉回以前的頁面。其中第一次需要用戶操作,點擊是否同意QQ授權;第二次不需要用戶操作,自動進行第三次跳轉。

簡單模式的授權

簡單模式是不利用“重定向URI”,直接向認證服務器中申請token的模式。還是以豆瓣和QQ為例,它的步驟如下:

  1. 用戶訪問豆瓣,豆瓣將用戶導向QQ的認證服務器頁面。
  2. 用戶選擇同意QQ的授權。
  3. QQ的認證服務器頁面將用戶導向豆瓣指定的“重定向URI”,並且直接在URI的hash部分附帶了令牌(token)。
  4. “重定向URI”利用令牌(token)向QQ的資源服務器申請用戶信息,然後在豆瓣註冊此用戶。
  5. 用戶在豆瓣的註冊完成。(不需要用戶填寫任何用戶信息)

有下面幾點需要註意:

  1. 和授權碼模式的授權的區別是,沒有授權碼,從而沒有檢測服務器id和密碼。

  2. 黑客可以通過這2種方式進行攻擊:1.黑客直接截取這個token。2.黑客偽造一個第三方網站,然後引導用戶在偽造的第三方網站進行授權,最後就能理所當然的得到token了。授權碼模式的授權可以防止第二種攻擊,但是同樣不能防止第一種攻擊(可以利用https加強安全性)。

  3. 授權碼模式的授權和簡單模式的授權為了防止CSRF攻擊,都會在跳轉的URI上面加一個state參數來進行校驗。

  4. 微信的靜默授權和這個差不多,只是沒有第二步,直接默認同意授權。

  5. URL跳轉才會在safari瀏覽器下面留下白條,非URL的URI跳轉不會。由於微信的非靜默授權會跳轉到一個用戶確認是否進行授權的URL,所以非靜默授權會留下白條,靜默授權不會。(不是因為有code才留下白條的)

  6. 為什麽要在hash部分附帶令牌?因為瀏覽器處理hash參數的時候不會使頁面重新加載。(雖然說新的history api能夠解決這個問題。)

  7. 如果認證服務器不支持跨域,那麽只能用簡單模式的授權。因為授權碼模式的授權會提交服務器id,密碼和code到認證服務器,所以需要認證服務器支持跨域的post請求;但是簡單模式的授權全部是URI跳轉,所以不用擔心跨域。

用戶認證和授權