WEB後臺--基於Token的WEB後臺登陸認證機制(轉)

WEB後臺--基於Token的WEB後臺登陸認證機制(並講解其餘認證機制以及cookie和session機制)

繼續這一個系列,基於Token的WEB後臺登陸認證機制(並講解cookie和session機制)。每一個後端不得不解決的認證問題。

本系列:

(一)J2EE項目系列(三)–Spring Data JPA+Spring+SpringMVC+Maven快速開發(1)項目架構

(二) J2EE項目系列(三)–Spring Data JPA+Spring+SpringMVC+Maven快速開發(2)多個第三方服務端接入之雲旺IM

(三) Java-解決實現JPA的hibernate自動建表的編碼問題


文章結構:(1)JWT(一種基於 token 的認證方案)介紹並介紹其餘幾大認證機制;(2)cookie和session機制;(3)Token機制相對於Cookie機制的好處;(4)JWT的Java實現;


1、JWT(一種基於 token 的認證方案)介紹:

(1)概述:JWT就是一種Token的編碼算法,服務器端負責根據一個密碼和算法生成Token,而後發給客戶端,客戶端只負責後面每次請求都在HTTP header裏面帶上這個Token,服務器負責驗證這個Token是否是合法的,有沒有過時等,並能夠解析出subject和claim裏面的數據。

(2)相關問題:

1.爲何用JWT?

JWT只經過算法實現對Token合法性的驗證,不依賴數據庫,Memcached的等存儲系統,所以能夠作到跨服務器驗證,只要密鑰和算法相同,不一樣服務器程序生成的Token能夠互相驗證。

2.JWT Token不須要持久化在任何NoSQL中,否則背離其算法驗證的初心

3.在退出登陸時怎樣實現JWT Token失效呢?

退出登陸, 只要客戶端端把Token丟棄就能夠了,服務器端不須要廢棄Token。

4.怎樣保持客戶端長時間保持登陸狀態?

服務器端提供刷新Token的接口, 客戶端負責按必定的邏輯刷新服務器Token。

5.服務器端是否應該從JWT中取出userid用於業務查詢?

REST API是無狀態的,意味着服務器端每次請求都是獨立的,即不依賴之前請求的結果,所以也不該該依賴JWT token作業務查詢, 應該在請求報文中單獨加個userid 字段。

爲了作用戶水平越權的檢查,能夠在業務層判斷傳入的userid和從JWT token中解析出的userid是否一致, 有些業務可能會容許查不一樣用戶的數據。


(3)其餘幾大認證機制:

1.HTTP Basic Auth

HTTP Basic Auth簡單點說明就是每次請求API時都提供用戶的username和password,簡言之,Basic Auth是配合RESTful API 使用的最簡單的認證方式,只需提供用戶名密碼便可,但因爲有把用戶名密碼暴露給第三方客戶端的風險,在生產環境下被使用的愈來愈少。所以,在開發對外開放的RESTful API時,儘可能避免採用HTTP Basic Auth

2.OAuth(開放受權)

是一個開放的受權標準,容許用戶讓第三方應用訪問該用戶在某一web服務上存儲的私密的資源(如照片,視頻,聯繫人列表),而無需將用戶名和密碼提供給第三方應用。
OAuth容許用戶提供一個令牌,而不是用戶名和密碼來訪問他們存放在特定服務提供者的數據。每個令牌受權一個特定的第三方系統(例如,視頻編輯網站)在特定的時段(例如,接下來的2小時內)內訪問特定的資源(例如僅僅是某一相冊中的視頻)。這樣,OAuth讓用戶能夠受權第三方網站訪問他們存儲在另外服務提供者的某些特定信息,而非全部內容。

這裏寫圖片描述

這種基於OAuth的認證機制適用於我的消費者類的互聯網產品,如社交類APP等應用,可是不太適合擁有自有認證權限管理的企業應用;

3.Cookie Auth

Cookie認證機制就是爲一次請求認證在服務端建立一個Session對象,同時在客戶端的瀏覽器端建立了一個Cookie對象;經過客戶端帶上來Cookie對象來與服務器端的session對象匹配來實現狀態管理的。默認的,當咱們關閉瀏覽器的時候,cookie會被刪除。但能夠經過修改cookie 的expire time使cookie在必定時間內有效;

2、cookie和session機制:

(1)概述:

Cookie和Session是爲了在無狀態的HTTP協議之上維護會話狀態,使得服務器能夠知道當前是和哪一個客戶在打交道。

Session是在服務端保存的一個數據結構,用來跟蹤用戶的狀態,這個數據能夠保存在集羣、數據庫、文件中;

Cookie是客戶端保存用戶信息的一種機制,用來記錄用戶的一些信息,也是實現Session的一種方式。

由於HTTP協議是無狀態的,即每次用戶請求到達服務器時,HTTP服務器並不知道這個用戶是誰、是否登陸過等。如今的服務器之因此知道咱們是否已經登陸,是由於服務器在登陸時設置了瀏覽器的Cookie!Session則是藉由Cookie而實現的更高層的服務器與瀏覽器之間的會話。

(2)cookie實現機制:

Cookie是由客戶端保存的小型文本文件,其內容爲一系列的鍵值對。 Cookie是由HTTP服務器設置的,保存在瀏覽器中, 在用戶訪問其餘頁面時,會在HTTP請求中附上該服務器以前設置的Cookie。

Cookie的傳遞流程

1.瀏覽器向某個URL發起HTTP請求(能夠是任何請求,好比GET一個頁面、POST一個登陸表單等)
2.對應的服務器收到該HTTP請求,並計算應當返回給瀏覽器的HTTP響應。(HTTP響應包括請求頭和請求體兩部分)
3.在響應頭加入Set-Cookie字段,它的值是要設置的Cookie。
4.瀏覽器收到來自服務器的HTTP響應。
5.瀏覽器在響應頭中發現Set-Cookie字段,就會將該字段的值保存在內存或者硬盤中。(Set-Cookie字段的值能夠是不少項Cookie,每一項均可以指定過時時間Expires。 默認的過時時間是用戶關閉瀏覽器時。)
6.瀏覽器下次給該服務器發送HTTP請求時, 會將服務器設置的Cookie附加在HTTP請求的頭字段Cookie中。(瀏覽器能夠存儲多個域名下的Cookie,但只發送當前請求的域名曾經指定的Cookie, 這個域名也能夠在Set-Cookie字段中指定)。)
7.服務器收到這個HTTP請求,發現請求頭中有Cookie字段, 便知道以前就和這個用戶打過交道了.
8.過時的Cookie會被瀏覽器刪除。

總之,服務器經過Set-Cookie響應頭字段來指示瀏覽器保存Cookie, 瀏覽器經過Cookie請求頭字段來告訴服務器以前的狀態。 Cookie中包含若干個鍵值對,每一個鍵值對能夠設置過時時間。

Cookie提供了一種手段使得HTTP請求能夠附加當前狀態, 現今的網站也是靠Cookie來標識用戶的登陸狀態的:

1.用戶提交用戶名和密碼的表單,這一般是一個POST HTTP請求。
2.服務器驗證用戶名與密碼,若是合法則返回200(OK)並設置Set-Cookie爲authed=true。
3.瀏覽器存儲該Cookie。
4.瀏覽器發送請求時,設置Cookie字段爲authed=true。
5.服務器收到第二次請求,從Cookie字段得知該用戶已經登陸。 按照已登陸用戶的權限來處理這次請求。

問題是什麼??風險是什麼??

咱們知道能夠發送HTTP請求的不僅是瀏覽器,不少HTTP客戶端軟件(包括curl、Node.js)均可以發送任意的HTTP請求,能夠設置任何頭字段。 假如咱們直接設置Cookie字段爲authed=true併發送該HTTP請求, 服務器豈不是被欺騙了?這種攻擊很是容易,Cookie是能夠被篡改的

服務器能夠爲每一個Cookie項生成簽名,因爲用戶篡改Cookie後沒法生成對應的簽名, 服務器即可以得知用戶對Cookie進行了篡改。

例子:一個簡單的校驗過程:

1.在服務器中配置一個鮮爲人知的字符串(咱們叫它Secret),好比:x$sfz32。
2.當服務器須要設置Cookie時(好比authed=false),不只設置authed的值爲false, 在值的後面進一步設置一個簽名,最終設置的Cookie是authed=false|6hTiBl7lVpd1P。
3.簽名6hTiBl7lVpd1P是這樣生成的:Hash(‘x$sfz32’+’true’)。 要設置的值與Secret相加再取哈希。
5.用戶在發送HTTP請求時,篡改了authed值,設置頭字段Cookie: authed=true|???。 由於用戶不知道Secret,沒法生成簽名,只能隨便填一個。
6.服務器收到HTTP請求,發現Cookie: authed=true|???。服務器開始進行校驗: Hash(‘true’+’x$sfz32’),便會發現用戶提供的簽名不正確。

經過給Cookie添加簽名,使得服務器得以知道Cookie被篡改。可是!!仍是有風險!

由於Cookie是明文傳輸的, 只要服務器設置過一次authed=true|xxxx我不就知道true的簽名是xxxx了麼, 之後就能夠用這個簽名來欺騙服務器了。所以Cookie中最好不要放敏感數據。 通常來說Cookie中只會放一個Session Id,而Session存儲在服務器端。

(3)session的實現機制:

1.概述:Session 是存儲在服務器端的,避免了在客戶端Cookie中存儲敏感數據。 Session 能夠存儲在HTTP服務器的內存中,也能夠存在內存數據庫(如redis)中, 對於重量級的應用甚至能夠存儲在數據庫中。

例子:存儲在redis中的Session爲例,考察如何驗證用戶登陸狀態的問題。

1.用戶提交包含用戶名和密碼的表單,發送HTTP請求。

2.服務器驗證用戶發來的用戶名密碼。

3.若是正確則把當前用戶名(一般是用戶對象)存儲到redis中,並生成它在redis中的ID。

這個ID稱爲Session ID,經過Session ID能夠從Redis中取出對應的用戶對象, 敏感數據(好比authed=true)都存儲在這個用戶對象中。

4.設置Cookie爲sessionId=xxxxxx|checksum併發送HTTP響應, 仍然爲每一項Cookie都設置簽名。

5.用戶收到HTTP響應後,便看不到任何敏感數據了。在此後的請求中發送該Cookie給服務器。

6.服務器收到此後的HTTP請求後,發現Cookie中有SessionID,進行放篡改驗證。

7.若是經過了驗證,根據該ID從Redis中取出對應的用戶對象, 查看該對象的狀態並繼續執行業務邏輯。

實現上述過程,在Web應用中能夠直接得到當前用戶。 至關於在HTTP協議之上,經過Cookie實現了持久的會話。這個會話便稱爲Session。


3、Token認證機制相對於Cookie等機制的好處:

1. 支持跨域訪問: Cookie是不容許垮域訪問的,這一點對Token機制是不存在的,前提是傳輸的用戶認證信息經過HTTP頭傳輸。(垮域訪問:兩個域名之間不能跨過域名來發送請求或者請求數據)

2.無狀態(也稱:服務端可擴展行):Token機制在服務端不須要存儲session信息,由於Token 自身包含了全部登陸用戶的信息,只須要在客戶端的cookie或本地介質存儲狀態信息.

3.更適用CDN: 能夠經過內容分發網絡請求你服務端的全部資料(如:javascript,HTML,圖片等),而你的服務端只要提供API便可.

4.去耦: 不須要綁定到一個特定的身份驗證方案。Token能夠在任何地方生成,只要在你的API被調用的時候,你能夠進行Token生成調用便可.

5.更適用於移動應用: 當你的客戶端是一個原平生臺(iOS, Android,Windows 8等)時,Cookie是不被支持的(你須要經過Cookie容器進行處理),這時採用Token認證機制就會簡單得多。

6. CSRF:由於再也不依賴於Cookie,因此你就不須要考慮對CSRF(跨站請求僞造)的防範。

7.性能: 一次網絡往返時間(經過數據庫查詢session信息)總比作一次HMACSHA256計算 的Token驗證和解析要費時得多.

8.不須要爲登陸頁面作特殊處理: 若是你使用Protractor 作功能測試的時候,再也不須要爲登陸頁面作特殊處理.

9.基於標準化:你的API能夠採用標準化的 JSON Web Token (JWT). 這個標準已經存在多個後端庫(.NET, Ruby, Java,Python, PHP)和多家公司的支持(如:Firebase,Google, Microsoft).


4、JWT的Java實現:

概述:一個JWT實際上就是一個字符串,它由三部分組成,頭部、載荷與簽名。這裏咱們只使用簡單的載荷,並將JSON對象進行base64編碼獲得token

過程:登陸爲例子

第一次認證:第一次登陸,用戶從瀏覽器輸入用戶名/密碼,提交後到服務器的登陸處理的Action層(controller)
Login Action調用認證服務進行用戶名密碼認證,若是認證經過,Login Action層調用用戶信息服務獲取用戶信息(包括完整的用戶信息及對應權限信息);
返回用戶信息後,Login Action從配置文件再通過工具類處理獲取Token簽名生成的祕鑰信息,進行Token的生成
生成Token的過程當中能夠調用第三方的JWT Lib生成簽名後的JWT數據;
完成JWT數據簽名後,將其設置到COOKIE對象中,並重定向到首頁,完成登陸過程;

請求認證

使用:基於Token的認證機制會在每一次請求中都帶上完成簽名的Token信息,這個Token信息可能在COOKIE中,也可能在HTTP的Authorization頭中;

注意:

客戶端(APP客戶端或瀏覽器)經過GET或POST請求訪問資源(頁面或調用API);
認證服務做爲一個Middleware HOOK 對請求進行攔截,首先在cookie中查找Token信息,若是沒有找到,則在HTTP Authorization Head中查找;
若是找到Token信息,則根據配置文件中的簽名加密祕鑰,調用JWT Lib對Token信息進行解密和解碼;
完成解碼並驗證簽名經過後,對Token中的exp、nbf、aud等信息進行驗證;
所有經過後,根據獲取的用戶的角色權限信息,進行對請求的資源的權限邏輯判斷;
若是權限邏輯判斷經過則經過Response對象返回;不然則返回HTTP 401;

(1)使用JWT的包:maven導入

<!--JSON WEB TOKEN --> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.6.0</version> </dependency>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

(2)一個生成token的工具類:

public class JavaWebToken { private static Logger log = Logger.getLogger(JavaWebToken.class); private static Key getKeyInstance() { // return MacProvider.generateKey(); //We will sign our JavaWebToken with our ApiKey secret SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256; byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary("APP"); Key signingKey = new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm.getJcaName()); return signingKey; } public static String createJavaWebToken(Map<String, Object> claims) { return Jwts.builder().setClaims(claims).signWith(SignatureAlgorithm.HS256, getKeyInstance()).compact(); } public static Map<String, Object> verifyJavaWebToken(String jwt) { try { Map<String, Object> jwtClaims = Jwts.parser().setSigningKey(getKeyInstance()).parseClaimsJws(jwt).getBody(); return jwtClaims; } catch (Exception e) { log.error("json web token verify failed"); return null; } } } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

(3)一個從request拿去session,而且解密session獲得token獲得用戶id的類

public class AuthUtil { private static Map<String, Object> getClientLoginInfo(HttpServletRequest request) throws Exception { Map<String, Object> r = new HashMap<>(); String sessionId = request.getHeader("sessionId"); if (sessionId != null) { r = decodeSession(sessionId); return r; } throw new Exception("session解析錯誤"); } public static Long getUserId(HttpServletRequest request) throws Exception { return Long.valueOf((Integer)getClientLoginInfo(request).get("userId")); } /** * session解密 */ public static Map<String, Object> decodeSession(String sessionId) { try { return verifyJavaWebToken(sessionId); } catch (Exception e) { System.err.println(""); return null; } } } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

使用例子:

登陸的時候把信息放進session,存到map裏,再交由JWT獲得token保存起來

這裏寫圖片描述

//登陸 @RequestMapping(value = "/login", method = {RequestMethod.GET, RequestMethod.POST}, produces = "text/html;charset=UTF-8") public String login(String account) { User user = userService.login(account); DTO dto = new DTO(); if (user == null) { dto.code = "-1"; dto.msg = "Have not registered"; } else { //把用戶登陸信息放進Session Map<String, Object> loginInfo = new HashMap<>(); loginInfo.put("userId", user.getId()); String sessionId = JavaWebToken.createJavaWebToken(loginInfo); System.out.println("sessionID"+sessionId); dto.data = sessionId; } return JSON.toJSONString(dto); }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

用戶登陸之後,其餘的用戶性知道的操做就可使用token進行了,安全快捷方便:

這裏寫圖片描述

//修改暱稱 @RequestMapping(value = "/updateName", method = {RequestMethod.GET, RequestMethod.POST}) public String updateName(HttpServletRequest request, String name) { DTO dto = new DTO(); try { //從session拿到token,再解密獲得userid Long userId = AuthUtil.getUserId(request); boolean userIsExist = userService.updateName(userId, name); if (userIsExist == false) { dto.code = "-1"; dto.msg = "Have not updateAvatar"; } } catch (Exception e) { e.printStackTrace(); } return JSON.toJSONString(dto); }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

這就是token機制的登陸認證功能簡單實現了,安全機制等,之後會有博客補充。


源碼下載:WEB後臺–基於Token的WEB後臺登陸認證機制(並講解其餘認證機制以及cookie和session機制)

好了,WEB後臺–基於Token的WEB後臺登陸認證機制(並講解其餘認證機制以及cookie和session機制)講完了。本博客是這個系列的第四篇,講的是一個簡單的登陸機制實現。另外,這個系列還有一些我在外包項目過程當中作的優化,雖然核心算法跟安全處理不能夠公佈,但我會盡快出完給你們,分享經驗給你們。歡迎在下面指出錯誤,共同窗習!!你的點贊是對我最好的支持!!

更多內容,能夠訪問JackFrost的博客

相關文章
相關標籤/搜索