A. Spring security OAuth2 深入解析
話不多說,先上圖:
分析一波:
其實不管微信或者QQ大體上都是使用這種OAuth2的基本流程:
OAuth2 在服務提供者上可分為兩類:
註:這兩者有時候可能存在同一個應用程序中(即SOA架構)。在Spring OAuth中可以簡便的將其分配到兩個應用中(即微服務),而且可多個資源獲取服務共享一個授權認證服務。
主要的操作:
分析一波:
1) 第一步操作 :
註:其中 client_id和 client_secret都是授權伺服器發送給第三方應用的,如:微信等一系列授權,在其平台上注冊,獲取其appid和secret同樣道理(個人理解為賬號密碼)。
既然是賬號秘密,總不能以get請求,也太不安全了。因此,OAuth2要求該請求必須是POST請求,同時,還必須時HTTPS服務,以此保證獲取到的安全憑證(Access Token)的安全性。
2) 第二步操作 :
3) 第三步操作 :
主要的操作:
spring OAuth2中,我們配置一個授權認證服務,我們最主要有以下三點:
spring中有三個配置與這三點一一對應:
除了上面說到的 client_id和 client_secret,還需要一些服務附帶一些授權認證參數。
1). Grant Type
其實OAuth2不僅提供授權碼(code)這種格式授權方式,還提供幾個其他類型。其中用Grant Type代表當前授權的類型。 Grant Type包括:
2). scope
其實授權賦予第三方用戶可以在資源伺服器獲取資源,經常就是調取Api請求附帶令牌,然而調取api有增刪查改等功能,而scopes的值就是all(全部許可權),read,write等許可權。就是第三方訪問資源的一個許可權,訪問范圍。
3). accessTokenValiditySeconds
還可以設置accessTokenValiditySeconds屬性來設置Access Token的存活時間。
AccessToken的存在意義:
1).
提供了對AccessToken的相關操作創建、刷新、獲取。
2). DefaultTokenServices
竟然可以操作AccessToken,那麼OAuth2就默認為我們提供了一個默認的DefaultTokenServices。包含了一些有用實現,可以使用它來修改令牌的格式和令牌的存儲等,但是生成的token是隨機數。
3). TokenStore
創建AccessToken完之後,除了發放給第三方,肯定還得保存起來,才可以使用。因此,TokenStore為我們完成這一操作,將令牌(AccessToken)保存或持久化。
TokenStore也有一個默認的實現類InMemoryTokenStore,從名字就知道是通過保存到內存進而實現保存Access Token。
TokenStore的實現有多種類型,可以根據業務需求更改Access Token的保存類型:
4). JWT Token
想使用jwt令牌,需要在授權服務中配置JwtTokenStore。之前說了,jwt將一些信息數據編碼後存放在令牌,那麼其實在傳輸的時候是很不安全的,所以Spring OAuth2提供了JwtAccessTokenConverter來懟令牌進行編碼和解碼。適用JwtAccessTokenConverter可以自定義秘簽(SigningKey)。SigningKey用處就是在授權認證伺服器生成進行簽名編碼,在資源獲取伺服器根據SigningKey解碼校驗。
授權認證是使用AuthorizationEndpoint這個端點來進行控制,一般使用 來進行配置。
1).端點(endpoints)的相關屬性配置:
2).端點(endpoints)的授權url:
要授權認證,肯定得由url請求,才可以傳輸。因此OAuth2提供了配置授權端點的URL。
,還是這個配置對象進行配置,其中由一個pathMapping()方法進行配置授權端點URL路徑,默認提供了兩個參數defaultPath和customPath:
pathMapping的defaultPath有:
註:pathMapping的兩個參數都將以 "/" 字元為開始的字元串
實際上我們上面說到的端點,其實可以看成Controller,用於返回不同端點的響應內容。
授權服務的錯誤信息是使用標準的Spring MVC來進行處理的,也就是 @ExceptionHandler 註解的端點方法,我們可以提供一個 對象。最好的方式是改變響應的內容而不是直接進行渲染。
資源伺服器,其實就是存放一些受令牌保護的資源,只有令牌並且有效正確才能獲取到資源。 內部是通過Spring OAuth2的Spring Security Authentication filter 的過濾鏈來進行保護。
我們可以繼承,來使用 進行相關配置。
ResourceServerTokenServices 是組成授權服務的另一半。
1).若是資源伺服器和授權服務在同一個應用,可以使用DefaultTokenServices
2).若是分離的。ResourceServerTokenServices必須知道令牌的如何解碼。
ResourceServerTokenServices解析令牌的方法:
註:授權認證服務需要把/oauth/check_toke暴露出來,並且附帶上許可權訪問。
(~ ̄▽ ̄)~未完待續... ...
B. JWT token封裝以及自動刷新方案建議
什麼是JWT
pom.xml
JWTUtil.java
用戶登錄操作
在前後分離場景下,越來越多的項目使用jwt token作為介面的安全機制,但存在jwt過期後,用戶無法直接感知,假如在用戶操作頁面期間,突然提示登錄,則體驗很不友好,所以就有了token自動刷新需求;
方案:前端控制檢測token,無感知刷新
用戶登錄成功的時候,一次性給他兩個Token,分別為AccessToken和RefreshToken
AccessToken有效期較短,比如1天或者5天,用於正常請求
RefreshToken有效期可以設置長一些,例如10天、20天,作為刷新AccessToken的憑證
刷新方案:當AccessToken即將過期的時候,例如提前30分鍾,客戶端利用RefreshToken請求指定的API獲取新的AccessToken並更新本地存儲中的AccessToken
核心邏輯
1、登錄成功後,jwt生成AccessToken; UUID生成RefreshToken並存儲在服務端redis中,設置過期時間
2、介面返回3個欄位AccessToken/RefreshToken/訪問令牌過期時間戳
3、由於RefreshToken存儲在服務端redis中,假如這個RefreshToken也過期,則提示重新登錄;
老王的疑問:RefreshToken有效期那麼長,和直接將AccessToken的有效期延長有什麼區別
答:RefreshToken不像AccessToken那樣在大多數請求中都被使用,主要是本地檢測accessToken快過期的時候才使用,
一般本地存儲的時候,也不叫refreshToken,前端可以取個別名,混淆代碼讓攻擊者不能直接識別這個就是刷新令牌
缺點:前端每次請求需要判斷token距離過期時間
優點:後端壓力小,代碼邏輯改動不大
刷新token方法未實現。
C. 請教JWT 的 Token 本地儲存和前端請求問題
token可以存localStorage裡面,至於你說的前端請求問題完全不知道是什麼意思。
D. JWT生成token及過期處理方案
## 業務場景
在前後分離場景下,越來越多的項目使用token作為介面的安全機制,APP端或者WEB端(使用VUE、REACTJS等構建)使用token與後端介面交互,以達到安全的目的。本文結合stackoverflow以及本身項目實踐,試圖總結出一個通用的,可落地的方案。
## 基本思路
- 單個token
1. token(A)過期設置為15分鍾
2. 前端發起請求,後端驗證token(A)是否過期;如果過期,前端發起刷新token請求,後端設置已再次授權標記為true,請求成功
3. 前端發起請求,後端驗證再次授權標記,如果已經再次授權,則拒絕刷新token的請求,請求成功
4. 如果前端每隔72小時,必須重新登錄,後端檢查用戶最後一次登錄日期,如超過72小時,則拒絕刷新token的請求,請求失敗
- 授權token加上刷新token
用戶僅登錄一次,用戶改變密碼,則廢除token,重新登錄
## 1.0實現
1.登錄成功,返回access\_token和refresh\_token,客戶端緩存此兩種token;
2.使用access_token請求介面資源,成功則調用成功;如果token超時,客戶端
攜帶refresh\_token調用中間件介面獲取新的access\_token;
3.中間件接受刷新token的請求後,檢查refresh_token是否過期。
如過期,拒絕刷新,客戶端收到該狀態後,跳轉到登錄頁;
如未過期,生成新的access\_token和refresh\_token並返回給客戶端(如有可能,讓舊的refresh\_token失效),客戶端攜帶新的access\_token重新調用上面的資源介面。
4.客戶端退出登錄或修改密碼後,調用中間件注銷舊的token(使access\_token和refresh\_token失效),同時清空客戶端的access\_token和refresh\_toke。
後端表
id user\_id client\_id client\_secret refresh\_token expire\_in create\_date del_flag
## 2.0實現
場景: access\_token訪問資源 refresh\_token授權訪問 設置固定時間X必須重新登錄
1.登錄成功,後台jwt生成access\_token(jwt有效期30分鍾)和refresh\_token(jwt有效期15天),並緩存到redis(hash-key為token,sub-key為手機號,value為設備唯一編號(根據手機號碼,可以人工廢除全部token,也可以根據sub-key,廢除部分設備的token。),設置過期時間為1個月,保證最終所有token都能刪除),返回後,客戶端緩存此兩種token;
2.使用access\_token請求介面資源,校驗成功且redis中存在該access\_token(未廢除)則調用成功;如果token超時,中間件刪除access\_token(廢除);客戶端再次攜帶refresh\_token調用中間件介面獲取新的access_token;
3.中間件接受刷新token的請求後,檢查refresh_token是否過期。
如過期,拒絕刷新,刪除refresh_token(廢除); 客戶端收到該狀態後,跳轉到登錄頁;
如未過期,檢查緩存中是否有refresh\_token(是否被廢除),如果有,則生成新的access\_token並返回給客戶端,客戶端接著攜帶新的access_token重新調用上面的資源介面。
4.客戶端退出登錄或修改密碼後,調用中間件注銷舊的token(中間件刪除access\_token和refresh\_token(廢除)),同時清空客戶端側的access\_token和refresh\_toke。
5.如手機丟失,可以根據手機號人工廢除指定用戶設備關聯的token。
6.以上3刷新access_token可以增加根據登錄時間判斷最長X時間必須重新登錄,此時則拒絕刷新token。(拒絕的場景:失效,長時間未登錄,頻繁刷新)
2.0 變動
1.登錄
2.登錄攔截器
3.增加刷新access_token介面
4.退出登錄
5.修改密碼
## 3.0實現
場景:自動續期 長時間未使用需重新登錄
1.登錄成功,後台jwt生成access\_token(jwt有效期30分鍾),並緩存到redis(hash-key為access\_token,sub-key為手機號,value為設備唯一編號(根據手機號碼,可以人工廢除全部token),設置access_token過期時間為7天,保證最終所有token都能刪除),返回後,客戶端緩存此token;
2.使用access\_token請求介面資源,校驗成功且redis中存在該access\_token(未廢除)則調用成功;如果token超時,中間件刪除access\_token(廢除),同時生成新的access\_token並返回。客戶端收到新的access_token,
再次請求介面資源。
3.客戶端退出登錄或修改密碼後,調用中間件注銷舊的token(中間件刪除access\_token(廢除)),同時清空客戶端側的access\_token。
4.以上2 可以增加根據登錄時間判斷最長X時間必須重新登錄,此時則拒絕刷新token。(拒絕的場景:長時間未登錄,頻繁刷新)
5.如手機丟失,可以根據手機號人工廢除指定用戶設備關聯的token。
3.0 變動
1.登錄
2.登錄攔截器
3.退出登錄
4.修改密碼
1.3 場景:token過期重新登錄 長時間未使用需重新登錄
1.登錄成功,後台jwt生成access\_token(jwt有效期7天),並緩存到redis,key為 "user\_id:access\_token",value為access\_token(根據用戶id,可以人工廢除指定用戶全部token),設置緩存過期時間為7天,保證最終所有token都能刪除,請求返回後,客戶端緩存此access_token;
2.使用access\_token請求介面資源,校驗成功且redis中存在該access\_token(未廢除)則調用成功;如果token超時,中間件刪除access\_token(廢除),同時生成新的access\_token並返回。客戶端收到新的access_token,
再次請求介面資源。
3.客戶端退出登錄或修改密碼後,調用中間件注銷舊的token(中間件刪除access\_token(廢除)),同時清空客戶端側的access\_token。
4.以上2 可以增加根據登錄時間判斷最長X時間必須重新登錄,此時則拒絕刷新token。(拒絕的場景:長時間未登錄,頻繁刷新)
5.如手機丟失,可以根據手機號人工廢除指定用戶設備關聯的token。
1.3 變動
1.登錄
2.登錄攔截器
3.退出登錄
4.修改密碼
# 解決方案
2.0 場景: access\_token訪問資源 refresh\_token授權訪問 設置固定時間X必須重新登錄
1.登錄成功,後台jwt生成access\_token(jwt有效期30分鍾)和refresh\_token(jwt有效期15天),並緩
存到redis(hash-key為token,sub-key為手機號,value為設備唯一編號(根據手機號碼,可以人工廢除全
部token,也可以根據sub-key,廢除部分設備的token。),設置過期時間為1個月,保證最終所有token都
能刪除),返回後,客戶端緩存此兩種token;
2.使用access\_token請求介面資源,校驗成功且redis中存在該access\_token(未廢除)則調用成功;如果
token超時,中間件刪除access\_token(廢除);客戶端再次攜帶refresh\_token調用中間件介面獲取新的
access_token;
3.中間件接受刷新token的請求後,檢查refresh_token是否過期。
如過期,拒絕刷新,刪除refresh_token(廢除); 客戶端收到該狀態後,跳轉到登錄頁;
如未過期,檢查緩存中是否有refresh\_token(是否被廢除),如果有,則生成新的access\_token並返回給
客戶端,客戶端接著攜帶新的access_token重新調用上面的資源介面。
4.客戶端退出登錄或修改密碼後,調用中間件注銷舊的token(中間件刪除access\_token和refresh\_token(
廢除)),同時清空客戶端側的access\_token和refresh\_toke。
5.如手機丟失,可以根據手機號人工廢除指定用戶設備關聯的token。
6.以上3刷新access_token可以增加根據登錄時間判斷最長X時間必須重新登錄,此時則拒絕刷新token。(
拒絕的場景:失效,長時間未登錄,頻繁刷新)
2.0 變動
1.登錄
2.登錄攔截器
3.增加刷新access_token介面
4.退出登錄
5.修改密碼
3.0 場景:自動續期 長時間未使用需重新登錄
1.登錄成功,後台jwt生成access_token(jwt有效期30分鍾),並緩存到redis(hash-key為
access_token,sub-key為手機號,value為設備唯一編號(根據手機號碼,可以人工廢除全部token,也可以
根據sub-key,廢除部分設備的token。),設置access_token過期時間為1個月,保證最終所有token都能刪
除),返回後,客戶端緩存此token;
2.使用access\_token請求介面資源,校驗成功且redis中存在該access\_token(未廢除)則調用成功;如果
token超時,中間件刪除access\_token(廢除),同時生成新的access\_token並返回。客戶端收到新的
access_token,
再次請求介面資源。
3.客戶端退出登錄或修改密碼後,調用中間件注銷舊的token(中間件刪除access_token(廢除)),同時清
空客戶端側的access_token。
4.以上2 可以增加根據登錄時間判斷最長X時間必須重新登錄,此時則拒絕刷新token。(拒絕的場景:長
時間未登錄,頻繁刷新)
5.如手機丟失,可以根據手機號人工廢除指定用戶設備關聯的token。
3.0 變動
1.登錄
2.登錄攔截器
3.退出登錄
4.修改密碼
4.0 場景:token過期重新登錄 長時間未使用需重新登錄
1.登錄成功,後台jwt生成access_token(jwt有效期7天),並緩存到redis,key為
"user\_id:access\_token" + 用戶id,value為access_token(根據用戶id,可以人工廢除指定用戶全部
token),設置緩存過期時間為7天,保證最終所有token都能刪除,請求返回後,客戶端緩存此
access_token;
2.使用access\_token請求介面資源,校驗成功且redis中存在該access\_token(未廢除)則調用成功;如果
token超時,中間件刪除access\_token(廢除),同時生成新的access\_token並返回。客戶端收到新的
access_token,
再次請求介面資源。
3.客戶端退出登錄或修改密碼後,調用中間件注銷舊的token(中間件刪除access_token(廢除)),同時清
空客戶端側的access_token。
4.以上2 可以增加根據登錄時間判斷最長X時間必須重新登錄,此時則拒絕刷新token。(拒絕的場景:長
時間未登錄,頻繁刷新)
5.如手機丟失,可以根據手機號人工廢除指定用戶設備關聯的token。
4.0 變動
1.登錄
2.登錄攔截器
3.退出登錄
4.修改密碼
## 最終實現
### 後端
1. 在登錄介面中 如果校驗賬號密碼成功 則根據用戶id和用戶類型創建jwt token(有效期設置為-1,即永不過期),得到A
2. 更新登錄日期(當前時間new Date()即可)(業務上可選),得到B
3. 在redis中緩存key為ACCESS_TOKEN:userId:A(加上A是為了防止用戶多個客戶端登錄 造成token覆蓋),value為B的毫秒數(轉換成字元串類型),過期時間為7天(7 * 24 * 60 * 60)
4. 在登錄結果中返回json格式為{"result":"success","token": A}
5. 用戶在介面請求header中攜帶token進行登錄,後端在所有介面前置攔截器進行攔截,作用是解析token 拿到userId和用戶類型(用戶調用業務介面只需要傳token即可),
如果解析失敗(拋出SignatureException),則返回json(code = 0 ,info= Token驗證不通過, errorCode = '1001');
此外如果解析成功,驗證redis中key為ACCESS_TOKEN:userId:A 是否存在 如果不存在 則返回json(code = 0 ,info= 會話過期請重新登錄, errorCode = '1002');
如果緩存key存在,則自動續7天超時時間(value不變),實現頻繁登錄用戶免登陸。
6. 把userId和用戶類型放入request參數中 介面方法中可以直接拿到登錄用戶信息
7. 如果是修改密碼或退出登錄 則廢除access_tokens(刪除key)
### 前端(VUE)
1. 用戶登錄成功,則把username存入cookie中,key為loginUser;把token存入cookie中,key為accessToken
把token存入Vuex全局狀態中
2. 進入首頁
E. 請教JWT 的 Token 本地儲存和前端請求問題
點允許就可以了。不妨礙你什麼。
F. 來,科普一下JWT
1. JSON Web Token是什麼
JSON Web Token (JWT)是一個開放標准(RFC 7519),它定義了一種緊湊的、自包含的方式,用於作為JSON對象在各方之間安全地傳輸信息。該信息可以被驗證和信任,因為它是數字簽名的。
2. 什麼時候你應該用JSON Web Tokens
下列場景中使用JSON Web Token是很有用的:
Authorization (授權) : 這是使用JWT的最常見場景。一旦用戶登錄,後續每個請求都將包含JWT,允許用戶訪問該令牌允許的路由、服務和資源。單點登錄是現在廣泛使用的JWT的一個特性,因為它的開銷很小,並且可以輕松地跨域使用。
Information Exchange (信息交換) : 對於安全的在各方之間傳輸信息而言,JSON Web Tokens無疑是一種很好的方式。因為JWTs可以被簽名,例如,用公鑰/私鑰對,你可以確定發送人就是它們所說的那個人。另外,由於簽名是使用頭和有效負載計算的,您還可以驗證內容沒有被篡改。
3. JSON Web Token的結構是什麼樣的
JSON Web Token由三部分組成,它們之間用圓點(.)連接。這三部分分別是:
因此,一個典型的JWT看起來是這個樣子的:
接下來,具體看一下每一部分:
Header
header典型的由兩部分組成:token的類型(「JWT」)和演算法名稱(比如:HMAC SHA256或者RSA等等)。
例如:
然後,用Base64對這個JSON編碼就得到JWT的第一部分
Payload
Public claims : 可以隨意定義。
下面是一個例子:
對payload進行Base64編碼就得到JWT的第二部分
Signature
為了得到簽名部分,你必須有編碼過的header、編碼過的payload、一個秘鑰,簽名演算法是header中指定的那個,然對它們簽名即可。
例如:
簽名是用於驗證消息在傳遞過程中有沒有被更改,並且,對於使用私鑰簽名的token,它還可以驗證JWT的發送方是否為它所稱的發送方。
看一張官網的圖就明白了:
4. JSON Web Tokens是如何工作的
在認證的時候,當用戶用他們的憑證成功登錄以後,一個JSON Web Token將會被返回。此後,token就是用戶憑證了,你必須非常小心以防止出現安全問題。一般而言,你保存令牌的時候不應該超過你所需要它的時間。
無論何時用戶想要訪問受保護的路由或者資源的時候,用戶代理(通常是瀏覽器)都應該帶上JWT,典型的,通常放在Authorization header中,用Bearer schema。
header應該看起來是這樣的:
伺服器上的受保護的路由將會檢查Authorization header中的JWT是否有效,如果有效,則用戶可以訪問受保護的資源。如果JWT包含足夠多的必需的數據,那麼就可以減少對某些操作的資料庫查詢的需要,盡管可能並不總是如此。
如果token是在授權頭(Authorization header)中發送的,那麼跨源資源共享(CORS)將不會成為問題,因為它不使用cookie。
下面這張圖顯示了如何獲取JWT以及使用它來訪問APIs或者資源:
5. 基於Token的身份認證 與 基於伺服器的身份認證
5.1. 基於伺服器的身份認證
在討論基於Token的身份認證是如何工作的以及它的好處之前,我們先來看一下以前我們是怎麼做的:
HTTP協議是無狀態的,也就是說,如果我們已經認證了一個用戶,那麼他下一次請求的時候,伺服器不知道我是誰,我們必須再次認證
傳統的做法是將已經認證過的用戶信息存儲在伺服器上,比如Session。用戶下次請求的時候帶著Session ID,然後伺服器以此檢查用戶是否認證過。
這種基於伺服器的身份認證方式存在一些問題:
Sessions : 每次用戶認證通過以後,伺服器需要創建一條記錄保存用戶信息,通常是在內存中,隨著認證通過的用戶越來越多,伺服器的在這里的開銷就會越來越大。
Scalability : 由於Session是在內存中的,這就帶來一些擴展性的問題。
CORS : 當我們想要擴展我們的應用,讓我們的數據被多個移動設備使用時,我們必須考慮跨資源共享問題。當使用AJAX調用從另一個域名下獲取資源時,我們可能會遇到禁止請求的問題。
CSRF : 用戶很容易受到CSRF攻擊。
5.2. JWT與Session的差異
相同點是,它們都是存儲用戶信息;然而,Session是在伺服器端的,而JWT是在客戶端的。
Session方式存儲用戶信息的最大問題在於要佔用大量伺服器內存,增加伺服器的開銷。
而JWT方式將用戶狀態分散到了客戶端中,可以明顯減輕服務端的內存壓力。
Session的狀態是存儲在伺服器端,客戶端只有session id;而Token的狀態是存儲在客戶端。
5.3. 基於Token的身份認證是如何工作的
基於Token的身份認證是無狀態的,伺服器或者Session中不會存儲任何用戶信息。
雖然這一實現可能會有所不同,但其主要流程如下:
注意:
5.4. 用Token的好處
無狀態和可擴展性: Tokens存儲在客戶端。完全無狀態,可擴展。我們的負載均衡器可以將用戶傳遞到任意伺服器,因為在任何地方都沒有狀態或會話信息。
安全: Token不是Cookie。(The token, not a cookie.)每次請求的時候Token都會被發送。而且,由於沒有Cookie被發送,還有助於防止CSRF攻擊。即使在你的實現中將token存儲到客戶端的Cookie中,這個Cookie也只是一種存儲機制,而非身份認證機制。沒有基於會話的信息可以操作,因為我們沒有會話!
還有一點,token在一段時間以後會過期,這個時候用戶需要重新登錄。這有助於我們保持安全。還有一個概念叫token撤銷,它允許我們根據相同的授權許可使特定的token甚至一組token無效。
5.5. JWT與OAuth的區別
寫在最後:我為大家准備了一些適合於1-5年以上開發經驗的java程序員面試涉及到的絕大部分面試題及答案做成了文檔和學習筆記文件以及架構視頻資料免費分享給大家(包括Dubbo、Redis、Netty、zookeeper、Spring cloud、分布式、高並發等架構技術資料),希望可以幫助到大家。
G. jwt的token怎麼生存的
引入依賴包加密解密方法。
在生產環境中,一般jwt會保存用戶的名字和角色許可權等信息。可以將token寫到cookie里,每次前端訪問後台時,可以在攔截器或者過濾器取到token,然後解密,先判斷是否過期,過期就拋異常阻止其訪問。然後取出信息保存到threadLocal里,方便以後調用這些信息,當後台訪問完成後,從thredLocal刪除此用戶信息。
伺服器認證以後,生成一個JSON格式的對象返回給客戶端。之後客戶端與服務端通信的時候,都要發回這個JSON對象。伺服器完全根據這個對象認證用戶身份。
H. 前端登陸實現
四種方式
Cookie 出現的原因: HTTP 協議是無狀態的,每次請求都會建立一個新的鏈接,請求結束就會斷開鏈接,優點就是可以節省鏈接資源,缺點就是無法保存用戶狀態。Cookie 的出現就是為了解決這個問題。
Cookie 是存儲在瀏覽器中的,可以通過 Js 和 set-cookie 這個響應欄位來進行設置。
cookie 的限制:
有了 cookie 之後,服務端就可以從客戶端獲取到信息,如果需要對信息進行驗證,那麼還需要 session
服務端在收到客戶端的請求之後,會在伺服器中開辟一片內存空間來存放 session
第一次登陸之後,下次再訪問的時候就會攜帶這個 cookie,服務端就可以根據 sessionId 進行驗證用戶是否登陸(判斷這個 sessionId 和服務端保存的 sessionId 是否一致,是否有這個 sessionId 的記錄或者記錄是否有效)
客戶端瀏覽器訪問伺服器的時候,伺服器把客戶端信息以某種形式記錄在伺服器上。這就是 Session。客戶端瀏覽器再次訪問時只需要從該 Session 中查找該客戶的狀態就可以了。
Token 是 伺服器 生成的一個字元串,作為客戶端請求的一個令牌。第一次登陸之後,伺服器會生成一個 Token 返回給客戶端,客戶端後續訪問的時候,只需帶上這個 Token 進行身份認證
缺點
JWT(Json Web Token)
服務端不需要存儲 Token 那麼服務端是怎麼驗證客戶端傳遞過來的 Token 是否有效的呢?
答案:
Token 並不是雜亂無章的字元串,而是通過多種演算法拼接而成的字元串
header 部分指定了這個 Token 所使用的簽名演算法
payload 部分表明了這個 JWT 的意圖
signature 部分為 JWT 的簽名,主要是為了讓 JWT 不被隨意的篡改
簽名的部分有兩個步驟
一:
二:
最後的 Token 計算如下:
單點登陸指的是公司會搭建一個公共的認證中心,公司里的所有產品的認證都可以在這個認證中心中完成,一個產品在認證中心認證之後,再去訪問其他產品時就不需要再次認證
這個時候,由於 a.com 存在已登錄的 Cookie 信息,所以伺服器端直接認證成功。
這個時候由於認證中心存在之前登陸過的 cookie,所以不需要再輸入賬號密碼,直接從第四步開始執行
目前我們已經完成了單點登錄,在同一套認證中心的管理下,多個產品可以共享登錄態。現在我們需要考慮退出了,即:在一個產品中退出了登錄,怎麼讓其他的產品也都退出登錄?
原理也不難,其實就是在攜帶 ticket 去請求認證中心的時候,再去請求一下認證中心的退出登陸的 api 即可
當某個產品 c.com 退出登陸時
sso 就是一個集中地驗證系統。你項目內請求時,向 sso 發一個請求,他給你個 token 你扔到游覽器緩存里,請求的時候放在請求頭里帶著。和其他驗證介面一樣。 他好就好在,一個賬號在不同系統里都可以登錄,因為不同項目可以共用這個 token。並且通過 sso 集中管理一些用戶信息,你可以方便的拿用戶信息。
以微信為例子
I. 鑒權必須了解的 5 個兄弟:cookie、session、token、jwt、單點登錄
本文你將看到:
**「前端存儲」**這就涉及到一發、一存、一帶,發好辦,登陸介面直接返回給前端,存儲就需要前端想辦法了。
前端的存儲方式有很多。
有,cookie。cookie 也是前端存儲的一種,但相比於 localStorage 等其他方式,藉助 HTTP 頭、瀏覽器能力,cookie 可以做到前端無感知。一般過程是這樣的:
「配置:Domain / Path」
cookie 是要限制::「空間范圍」::的,通過 Domain(域)/ Path(路徑)兩級。
「配置:Expires / Max-Age」
cookie 還可以限制::「時間范圍」::,通過 Expires、Max-Age 中的一種。
「配置:Secure / HttpOnly」
cookie 可以限制::「使用方式」::。
**「HTTP 頭對 cookie 的讀寫」**回過頭來,HTTP 是如何寫入和傳遞 cookie 及其配置的呢?HTTP 返回的一個 Set-Cookie 頭用於向瀏覽器寫入「一條(且只能是一條)」cookie,格式為 cookie 鍵值 + 配置鍵值。例如:
那我想一次多 set 幾個 cookie 怎麼辦?多給幾個 Set-Cookie 頭(一次 HTTP 請求中允許重復)
HTTP 請求的 Cookie 頭用於瀏覽器把符合當前「空間、時間、使用方式」配置的所有 cookie 一並發給服務端。因為由瀏覽器做了篩選判斷,就不需要歸還配置內容了,只要發送鍵值就可以。
**「前端對 cookie 的讀寫」**前端可以自己創建 cookie,如果服務端創建的 cookie 沒加HttpOnly,那恭喜你也可以修改他給的 cookie。調用document.cookie可以創建、修改 cookie,和 HTTP 一樣,一次document.cookie能且只能操作一個 cookie。
調用document.cookie也可以讀到 cookie,也和 HTTP 一樣,能讀到所有的非HttpOnly cookie。
現在回想下,你刷卡的時候發生了什麼?
這種操作,在前後端鑒權系統中,叫 session。典型的 session 登陸/驗證流程:
**「Session 的存儲方式」**顯然,服務端只是給 cookie 一個 sessionId,而 session 的具體內容(可能包含用戶信息、session 狀態等),要自己存一下。存儲的方式有幾種:
「Session 的過期和銷毀」**很簡單,只要把存儲的 session 數據銷毀就可以。****「Session 的分布式問題」**通常服務端是集群,而用戶請求過來會走一次負載均衡,不一定打到哪台機器上。那一旦用戶後續介面請求到的機器和他登錄請求的機器不一致,或者登錄請求的機器宕機了,session 不就失效了嗎?這個問題現在有幾種解決方式。
但通常還是採用第一種方式,因為第二種相當於閹割了負載均衡,且仍沒有解決「用戶請求的機器宕機」的問題。**「node.js 下的 session 處理」**前面的圖很清楚了,服務端要實現對 cookie 和 session 的存取,實現起來要做的事還是很多的。在npm中,已經有封裝好的中間件,比如 express-session - npm,用法就不貼了。這是它種的 cookie:
express-session - npm 主要實現了:
session 的維護給服務端造成很大困擾,我們必須找地方存放它,又要考慮分布式的問題,甚至要單獨為了它啟用一套 Redis 集群。有沒有更好的辦法?
回過頭來想想,一個登錄場景,也不必往 session 存太多東西,那為什麼不直接打包到 cookie 中呢?這樣服務端不用存了,每次只要核驗 cookie 帶的「證件」有效性就可以了,也可以攜帶一些輕量的信息。這種方式通常被叫做 token。
token 的流程是這樣的:
**「客戶端 token 的存儲方式」 在前面 cookie 說過,cookie 並不是客戶端存儲憑證的唯一方式。token 因為它的「無狀態性」,有效期、使用限制都包在 token 內容里,對 cookie 的管理能力依賴較小,客戶端存起來就顯得更自由。但 web 應用的主流方式仍是放在 cookie 里,畢竟少操心。 「token 的過期」**那我們如何控制 token 的有效期呢?很簡單,把「過期時間」和數據一起塞進去,驗證時判斷就好。
編碼的方式豐儉由人。**「base64」**比如 node 端的 cookie-session - npm 庫
默認配置下,當我給他一個 userid,他會存成這樣:
這里的 eyJ1c2VyaWQiOiJhIn0=,就是 {"userid":"abb」} 的 base64 而已。 「防篡改」
是的。所以看情況,如果 token 涉及到敏感許可權,就要想辦法避免 token 被篡改。解決方案就是給 token 加簽名,來識別 token 是否被篡改過。例如在 cookie-session - npm 庫中,增加兩項配置:
這樣會多種一個 .sig cookie,裡面的值就是 {"userid":"abb」} 和 iAmSecret通過加密演算法計算出來的,常見的比如HMACSHA256 類 (System.Security.Cryptography) | Microsoft Docs。
好了,現在 cdd 雖然能偽造出eyJ1c2VyaWQiOiJhIn0=,但偽造不出 sig 的內容,因為他不知道 secret。**「JWT」**但上面的做法額外增加了 cookie 數量,數據本身也沒有規范的格式,所以 JSON Web Token Introction - jwt.io 橫空出世了。
它是一種成熟的 token 字元串生成方案,包含了我們前面提到的數據、簽名。不如直接看一下一個 JWT token 長什麼樣:
這串東西是怎麼生成的呢?看圖:
類型、加密演算法的選項,以及 JWT 標准數據欄位,可以參考 RFC 7519 - JSON Web Token (JWT)node 上同樣有相關的庫實現:express-jwt - npm koa-jwt - npm
token,作為許可權守護者,最重要的就是「安全」。業務介面用來鑒權的 token,我們稱之為 access token。越是許可權敏感的業務,我們越希望 access token 有效期足夠短,以避免被盜用。但過短的有效期會造成 access token 經常過期,過期後怎麼辦呢?一種辦法是,讓用戶重新登錄獲取新 token,顯然不夠友好,要知道有的 access token 過期時間可能只有幾分鍾。另外一種辦法是,再來一個 token,一個專門生成 access token 的 token,我們稱為 refresh token。
有了 refresh token 後,幾種情況的請求流程變成這樣:
如果 refresh token 也過期了,就只能重新登錄了。
session 和 token 都是邊界很模糊的概念,就像前面說的,refresh token 也可能以 session 的形式組織維護。狹義上,我們通常認為 session 是「種在 cookie 上、數據存在服務端」的認證方案,token 是「客戶端存哪都行、數據存在 token 里」的認證方案。對 session 和 token 的對比本質上是「客戶端存 cookie / 存別地兒」、「服務端存數據 / 不存數據」的對比。**「客戶端存 cookie / 存別地兒」**存 cookie 固然方便不操心,但問題也很明顯:
存別的地方,可以解決沒有 cookie 的場景;通過參數等方式手動帶,可以避免 CSRF 攻擊。 「服務端存數據 / 不存數據」
前面我們已經知道了,在同域下的客戶端/服務端認證系統中,通過客戶端攜帶憑證,維持一段時間內的登錄狀態。但當我們業務線越來越多,就會有更多業務系統分散到不同域名下,就需要「一次登錄,全線通用」的能力,叫做「單點登錄」。
簡單的,如果業務系統都在同一主域名下,比如wenku..com tieba..com,就好辦了。可以直接把 cookie domain 設置為主域名 .com,網路也就是這么乾的。
比如滴滴這么潮的公司,同時擁有didichuxing.com xiaojukeji.com didiglobal.com等域名,種 cookie 是完全繞不開的。這要能實現「一次登錄,全線通用」,才是真正的單點登錄。這種場景下,我們需要獨立的認證服務,通常被稱為 SSO。 「一次「從 A 系統引發登錄,到 B 系統不用登錄」的完整流程」
**「完整版本:考慮瀏覽器的場景」**上面的過程看起來沒問題,實際上很多 APP 等端上這樣就夠了。但在瀏覽器下不見得好用。看這里:
對瀏覽器來說,SSO 域下返回的數據要怎麼存,才能在訪問 A 的時候帶上?瀏覽器對跨域有嚴格限制,cookie、localStorage 等方式都是有域限制的。這就需要也只能由 A 提供 A 域下存儲憑證的能力。一般我們是這么做的:
圖中我們通過顏色把瀏覽器當前所處的域名標記出來。注意圖中灰底文字說明部分的變化。
謝謝大家哦
J. jwt中為啥用refresh_token去刷新access_token,直接把access_token的有效期設置長一點不行嗎
access_token使用頻率高,容易泄露,有效期風險就小。refresh_token使用頻率低,泄露風險小。另外,不是必須要有兩個token吧?