大家好,我是 Guide哥!端午已過,又要開始工作學習啦!
我發現有很多小夥伴對認證授權方面的知識不是特別瞭解,搞不清 Session 認證、JWT 以及 Cookie 這些概念。
所以,根據我根據日常對這部分學習已經在專案中的實際運用總結了這 13 個相關的問題並且附上了詳細的回答。希望能夠對大家有幫助!
認證 (Authentication) 和授權 (Authorization)的區別是什麼?
這是一個絕大多數人都會混淆的問題。首先先從讀音上來認識這兩個名詞,很多人都會把它倆的讀音搞混,所以我建議你先先去查一查這兩個單詞到底該怎麼讀,他們的具體含義是什麼。
說簡單點就是:
- 認證 (Authentication): 你是誰。
- 授權 (Authorization): 你有許可權幹什麼。
稍微正式點(囉嗦點)的說法就是 :
- Authentication(認證) 是驗證您的身份的憑據(例如使用者名稱/使用者 ID 和密碼),通過這個憑據,系統得以知道你就是你,也就是說系統存在你這個使用者。所以,Authentication 被稱為身份/使用者驗證。
- Authorization(授權) 發生在 Authentication(認證) 之後。授權嘛,光看意思大家應該就明白,它主要掌管我們訪問系統的許可權。比如有些特定資源只能具有特定許可權的人才能訪問比如 admin,有些對系統資源操作比如刪除、新增、更新只能特定人才具有。
認證 :
授權:
這兩個一般在我們的系統中被結合在一起使用,目的就是為了保護我們系統的安全性。
RBAC 模型瞭解嗎?
系統許可權控制最常採用的訪問控制模型就是 RBAC 模型 。
什麼是 RBAC 呢?
RBAC 即基於角色的許可權訪問控制(Role-Based Access Control)。這是一種通過角色關聯許可權,角色同時又關聯使用者的授權的方式。
簡單地說:一個使用者可以擁有若干角色,每一個角色有可以被分配若干許可權這樣,就構造成“使用者-角色-許可權” 的授權模型。在這種模型中,使用者與角色、角色與許可權之間構成了多對多的關係,如下圖
在 RBAC 中,許可權與角色相關聯,使用者通過成為適當角色的成員而得到這些角色的許可權。這就極大地簡化了許可權的管理。
通常 RBAC 下的許可權設計相關的表有5 張,其中有 2 張用於建立表之間的聯絡:
通過這個許可權模型,我們可以建立不同的角色併為不同的角色分配不同的許可權範圍(選單)。
通常來說,如果系統對於許可權控制要求比較嚴格的話,一般都會選擇使用 RBAC 模型來做許可權控制。
什麼是 Cookie ? Cookie 的作用是什麼?
Cookie
和 Session
都是用來跟蹤瀏覽器使用者身份的會話方式,但是兩者的應用場景不太一樣。
維基百科是這樣定義 Cookie
的:
Cookies
是某些網站為了辨別使用者身份而儲存在使用者本地終端上的資料(通常經過加密)。
簡單來說: Cookie
存放在客戶端,一般用來儲存使用者資訊。
下面是 Cookie
的一些應用案例:
- 我們在
Cookie
中儲存已經登入過的使用者資訊,下次訪問網站的時候頁面可以自動幫你登入的一些基本資訊給填了。除此之外,Cookie
還能儲存使用者首選項,主題和其他設定資訊。 - 使用
Cookie
儲存Session
或者Token
,向後端傳送請求的時候帶上Cookie
,這樣後端就能取到Session
或者Token
了。這樣就能記錄使用者當前的狀態了,因為 HTTP 協議是無狀態的。 Cookie
還可以用來記錄和分析使用者行為。舉個簡單的例子你在網上購物的時候,因為 HTTP 協議是沒有狀態的,如果伺服器想要獲取你在某個頁面的停留狀態或者看了哪些商品,一種常用的實現方式就是將這些資訊存放在Cookie
- ......
如何在專案中使用 Cookie 呢?
我這裡以 Spring Boot 專案為例。
1)設定 Cookie
返回給客戶端
@GetMapping("/change-username")
public String setCookie(HttpServletResponse response) {
// 建立一個 cookie
Cookie cookie = new Cookie("username", "Jovan");
//設定 cookie過期時間
cookie.setMaxAge(7 * 24 * 60 * 60); // expires in 7 days
//新增到 response 中
response.addCookie(cookie);
return "Username is changed!";
}
2) 使用 Spring 框架提供的 @CookieValue
註解獲取特定的 cookie 的值
@GetMapping("/")
public String readCookie(@CookieValue(value = "username", defaultValue = "Atta") String username) {
return "Hey! My username is " + username;
}
3) 讀取所有的 Cookie
值
@GetMapping("/all-cookies")
public String readAllCookies(HttpServletRequest request) {
Cookie[] cookies = request.getCookies();
if (cookies != null) {
return Arrays.stream(cookies)
.map(c -> c.getName() + "=" + c.getValue()).collect(Collectors.joining(", "));
}
return "No cookies";
}
更多關於如何在 Spring Boot 中使用 Cookie
的內容可以檢視這篇文章:How to use cookies in Spring Boot 。
Cookie 和 Session 有什麼區別?
Session
的主要作用就是通過服務端記錄使用者的狀態。 典型的場景是購物車,當你要新增商品到購物車的時候,系統不知道是哪個使用者操作的,因為 HTTP 協議是無狀態的。服務端給特定的使用者建立特定的 Session
之後就可以標識這個使用者並且跟蹤這個使用者了。
Cookie
資料儲存在客戶端(瀏覽器端),Session
資料儲存在伺服器端。相對來說 Session
安全性更高。為了保證 Cookie
中資訊的安全性,最好能將 Cookie
資訊加密然後使用到的時候再去伺服器端解密。
那麼,如何使用 Session
進行身份驗證?
如何使用 Session-Cookie 方案進行身份驗證?
很多時候我們都是通過 SessionID
來實現特定的使用者,SessionID
一般會選擇存放在 Redis 中。舉個例子:
- 使用者成功登陸系統,然後返回給客戶端具有
SessionID
的Cookie
- 當使用者向後端發起請求的時候會把
SessionID
帶上,這樣後端就知道你的身份狀態了。
關於這種認證方式更詳細的過程如下:
- 使用者向伺服器傳送使用者名稱、密碼、驗證碼用於登陸系統。
- 伺服器驗證通過後,伺服器為使用者建立一個
Session
,並將Session
資訊儲存起來。 - 伺服器向使用者返回一個
SessionID
,寫入使用者的Cookie
。 - 當使用者保持登入狀態時,
Cookie
將與每個後續請求一起被髮送出去。 - 伺服器可以將儲存在
Cookie
上的SessionID
與儲存在記憶體中或者資料庫中的Session
資訊進行比較,以驗證使用者的身份,返回給使用者客戶端響應資訊的時候會附帶使用者當前的狀態。
使用 Session
的時候需要注意下面幾個點:
- 依賴
Session
的關鍵業務一定要確保客戶端開啟了Cookie
。 - 注意
Session
的過期時間。
另外,Spring Session 提供了一種跨多個應用程式或例項管理使用者會話資訊的機制。如果想詳細瞭解可以檢視下面幾篇很不錯的文章:
- Getting Started with Spring Session
- Guide to Spring Session
- Sticky Sessions with Spring Session & Redis
多伺服器節點下 Session-Cookie 方案如何做?
Session-Cookie 方案在單體環境是一個非常好的身份認證方案。但是,當伺服器水平擴充成多節點時,Session-Cookie 方案就要面臨挑戰了。
舉個例子:假如我們部署了兩份相同的服務 A,B,使用者第一次登陸的時候 ,Nginx 通過負載均衡機制將使用者請求轉發到 A 伺服器,此時使用者的 Session 資訊儲存在 A 伺服器。結果,使用者第二次訪問的時候 Nginx 將請求路由到 B 伺服器,由於 B 伺服器沒有儲存 使用者的 Session 資訊,導致使用者需要重新進行登陸。
我們應該如何避免上面這種情況的出現呢?
有幾個方案可供大家參考:
- 某個使用者的所有請求都通過特性的雜湊策略分配給同一個伺服器處理。這樣的話,每個伺服器都儲存了一部分使用者的 Session 資訊。伺服器當機,其儲存的所有 Session 資訊就完全丟失了。
- 每一個伺服器儲存的 Session 資訊都是互相同步的,也就是說每一個伺服器都儲存了全量的 Session 資訊。每當一個伺服器的 Session 資訊發生變化,我們就將其同步到其他伺服器。這種方案成本太大,並且,節點越多時,同步成本也越高。
- 單獨使用一個所有伺服器都能訪問到的資料節點(比如快取)來存放 Session 資訊。為了保證高可用,資料節點儘量要避免是單點。
如果沒有 Cookie 的話 Session 還能用嗎?
這是一道經典的面試題!
一般是通過 Cookie
來儲存 SessionID
,假如你使用了 Cookie
儲存 SessionID
的方案的話, 如果客戶端禁用了 Cookie
,那麼 Session
就無法正常工作。
但是,並不是沒有 Cookie
之後就不能用 Session
了,比如你可以將 SessionID
放在請求的 url
裡面https://javaguide.cn/?Session_id=xxx
。這種方案的話可行,但是安全性和使用者體驗感降低。當然,為了你也可以對 SessionID
進行一次加密之後再傳入後端。
為什麼 Cookie 無法防止 CSRF 攻擊,而 Token 可以?
CSRF (Cross Site Request Forgery)一般被翻譯為 跨站請求偽造 。那麼什麼是 跨站請求偽造 呢?說簡單用你的身份去傳送一些對你不友好的請求。舉個簡單的例子:
小壯登入了某網上銀行,他來到了網上銀行的帖子區,看到一個帖子下面有一個連結寫著“科學理財,年盈利率過萬”,小壯好奇的點開了這個連結,結果發現自己的賬戶少了 10000 元。這是這麼回事呢?原來黑客在連結中藏了一個請求,這個請求直接利用小壯的身份給銀行傳送了一個轉賬請求,也就是通過你的 Cookie 向銀行發出請求。
<a src=http://www.mybank.com/Transfer?bankId=11&money=10000>科學理財,年盈利率過萬</>
上面也提到過,進行 Session
認證的時候,我們一般使用 Cookie
來儲存 SessionId
,當我們登陸後後端生成一個 SessionId
放在 Cookie 中返回給客戶端,服務端通過 Redis 或者其他儲存工具記錄儲存著這個 SessionId
,客戶端登入以後每次請求都會帶上這個 SessionId
,服務端通過這個 SessionId
來標示你這個人。如果別人通過 Cookie
拿到了 SessionId
後就可以代替你的身份訪問系統了。
Session
認證中 Cookie
中的 SessionId
是由瀏覽器傳送到服務端的,藉助這個特性,攻擊者就可以通過讓使用者誤點攻擊連結,達到攻擊效果。
但是,我們使用 Token
的話就不會存在這個問題,在我們登入成功獲得 Token
之後,一般會選擇存放在 localStorage
(瀏覽器本地儲存)中。然後我們在前端通過某些方式會給每個發到後端的請求加上這個 Token
,這樣就不會出現 CSRF 漏洞的問題。因為,即使有個你點選了非法連結傳送了請求到服務端,這個非法請求是不會攜帶 Token
的,所以這個請求將是非法的。
需要注意的是不論是 Cookie
還是 Token
都無法避免 跨站指令碼攻擊(Cross Site Scripting)XSS 。
跨站指令碼攻擊(Cross Site Scripting)縮寫為 CSS 但這會與層疊樣式表(Cascading Style Sheets,CSS)的縮寫混淆。因此,有人將跨站指令碼攻擊縮寫為 XSS。
XSS 中攻擊者會用各種方式將惡意程式碼注入到其他使用者的頁面中。就可以通過指令碼盜用資訊比如 Cookie
。
推薦閱讀:如何防止 CSRF 攻擊?—美團技術團隊
什麼是 Token?什麼是 JWT?
我們在前面的問題中探討了使用 Session
來鑑別使用者的身份,並且給出了幾個 Spring Session 的案例分享。 我們知道 Session
資訊需要儲存一份在伺服器端。這種方式會帶來一些麻煩,比如需要我們保證儲存 Session
資訊伺服器的可用性、不適合移動端(依賴 Cookie
)等等。
有沒有一種不需要自己存放 Session
資訊就能實現身份驗證的方式呢?使用 Token
即可!JWT (JSON Web Token) 就是這種方式的實現,通過這種方式伺服器端就不需要儲存 Session
資料了,只用在客戶端儲存服務端返回給客戶的 Token
就可以了,擴充套件性得到提升。
JWT 本質上就一段簽名的 JSON 格式的資料。由於它是帶有簽名的,因此接收者便可以驗證它的真實性。
下面是 RFC 7519 對 JWT 做的較為正式的定義。
JSON Web Token (JWT) is a compact, URL-safe means of representing claims to be transferred between two parties. The claims in a JWT are encoded as a JSON object that is used as the payload of a JSON Web Signature (JWS) structure or as the plaintext of a JSON Web Encryption (JWE) structure, enabling the claims to be digitally signed or integrity protected with a Message Authentication Code (MAC) and/or encrypted. ——JSON Web Token (JWT)
JWT 由 3 部分構成:
- Header : 描述 JWT 的後設資料,定義了生成簽名的演算法以及
Token
的型別。 - Payload : 用來存放實際需要傳遞的資料
- Signature(簽名) :伺服器通過
Payload
、Header
和一個金鑰(secret
)使用Header
裡面指定的簽名演算法(預設是 HMAC SHA256)生成。
如何基於 Token 進行身份驗證?
在基於 Token 進行身份驗證的的應用程式中,伺服器通過Payload
、Header
和一個金鑰(secret
)建立令牌(Token
)並將 Token
傳送給客戶端,客戶端將 Token
儲存在 Cookie 或者 localStorage 裡面,以後客戶端發出的所有請求都會攜帶這個令牌。你可以把它放在 Cookie 裡面自動傳送,但是這樣不能跨域,所以更好的做法是放在 HTTP Header 的 Authorization 欄位中:Authorization: Bearer Token
。
- 使用者向伺服器傳送使用者名稱和密碼用於登陸系統。
- 身份驗證服務響應並返回了簽名的 JWT,上面包含了使用者是誰的內容。
- 使用者以後每次向後端發請求都在
Header
中帶上 JWT。 - 服務端檢查 JWT 並從中獲取使用者相關資訊。
什麼是 SSO?
SSO(Single Sign On)即單點登入說的是使用者登陸多個子系統的其中一個就有權訪問與其相關的其他系統。舉個例子我們在登陸了京東金融之後,我們同時也成功登陸京東的京東超市、京東國際、京東生鮮等子系統。
什麼是 OAuth 2.0?
OAuth 是一個行業的標準授權協議,主要用來授權第三方應用獲取有限的許可權。而 OAuth 2.0 是對 OAuth 1.0 的完全重新設計,OAuth 2.0 更快,更容易實現,OAuth 1.0 已經被廢棄。詳情請見:rfc6749。
實際上它就是一種授權機制,它的最終目的是為第三方應用頒發一個有時效性的令牌 Token,使得第三方應用能夠通過該令牌獲取相關的資源。
OAuth 2.0 比較常用的場景就是第三方登入,當你的網站接入了第三方登入的時候一般就是使用的 OAuth 2.0 協議。
另外,現在 OAuth 2.0 也常見於支付場景(微信支付、支付寶支付)和開發平臺(微信開放平臺、阿里開放平臺等等)。
微信支付賬戶相關引數:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-yqIC91bs-1623925796543)(./images/basis-of-authority-certification/微信支付-fnglfdlgdfj.png)]
下圖是 Slack OAuth 2.0 第三方登入的示意圖:
推薦閱讀:
花了半個月寫的最新版 Java學習路線已近更新!可能是你看過最用心、最全面的 Java 後端學習路線。
我是 Guide哥,擁抱開源,喜歡烹飪。開源專案 JavaGuide 作者,Github:Snailclimb - Overview 。未來幾年,希望持續完善 JavaGuide,爭取能夠幫助更多學習 Java 的小夥伴!共勉!凎!點選檢視我的2020年工作彙報!
原創不易,歡迎點贊分享,歡迎關注 @JavaGuide,我會持續分享原創乾貨~
本回答為我本人原創,如需轉載,還請註明出處啊!