傳統身份驗證的方法
HTTP Basic Auth
HTTP Basic Auth簡單點說明就是每次請求API時都提供用戶的username和password,簡言之,Basic Auth是配合RESTful API 使用的最簡單的認證方式,只需提供用戶名密碼即可,但由於有把用戶名密碼暴露給第三方客戶端的風險,在生產環境下被使用的越來越少。因此,在開發對外開放的RESTful API時,盡量避免采用HTTP Basic Auth。
OAuth
OAuth(開放授權)是一個開放的授權標准,允許用戶讓第三方應用訪問該用戶在某一web服務上存儲的私密的資源(如照片,視頻,聯系人列表),而無需將用戶名和密碼提供給第三方應用。
OAuth允許用戶提供一個令牌,而不是用戶名和密碼來訪問他們存放在特定服務提供者的數據。每一個令牌授權一個特定的第三方系統(例如,視頻編輯網站)在特定的時段(例如,接下來的2小時內)內訪問特定的資源(例如僅僅是某一相冊中的視頻)。這樣,OAuth讓用戶可以授權第三方網站訪問他們存儲在另外服務提供者的某些特定信息,而非所有內容
下面是OAuth2.0的流程:
這種基於OAuth的認證機制適用於個人消費者類的互聯網產品,如社交類APP等應用,但是不太適合擁有自有認證權限管理的企業應用。
Cookie Auth
Cookie認證機制就是為一次請求認證在服務端創建一個Session對象,同時在客戶端的瀏覽器端創建了一個Cookie對象;通過客戶端帶上來Cookie對象來與服務器端的session對象匹配來實現狀態管理的。默認的,當我們關閉瀏覽器的時候,cookie會被刪除。但可以通過修改cookie 的expire time使cookie在一定時間內有效。
基於 Token 的身份驗證方法
使用基於 Token 的身份驗證方法,大概的流程是這樣的:
- 客戶端使用用戶名跟密碼請求登錄
- 服務端收到請求,去驗證用戶名與密碼
- 驗證成功后,服務端會簽發一個 Token然后保存(緩存或者數據庫),再把這個 Token 發送給客戶端
- 客戶端收到 Token 以后可以把它存儲起來,比如放在 Cookie 里或者 Local Storage 里
- 客戶端每次向服務端請求資源的時候需要帶着服務端簽發的 Token
- 服務端收到請求,然后去驗證客戶端請求里面帶着的 Token,如果驗證成功,就向客戶端返回請求的數據
Token機制相對於Cookie機制又有什么好處呢?
- 支持跨域訪問: Cookie是不允許垮域訪問的,這一點對Token機制是不存在的,前提是傳輸的用戶認證信息通過HTTP頭傳輸.
- 無狀態(也稱:服務端可擴展行):Token機制在服務端不需要存儲session信息,因為Token 自身包含了所有登錄用戶的信息,只需要在客戶端的cookie或本地介質存儲狀態信息.
- 更適用CDN: 可以通過內容分發網絡請求你服務端的所有資料(如:javascript,HTML,圖片等),而你的服務端只要提供API即可.
- 去耦: 不需要綁定到一個特定的身份驗證方案。Token可以在任何地方生成,只要在你的API被調用的時候,你可以進行Token生成調用即可.
- 更適用於移動應用: 當你的客戶端是一個原生平台(iOS, Android,Windows 8等)時,Cookie是不被支持的(你需要通過Cookie容器進行處理),這時采用Token認證機制就會簡單得多。
- CSRF:因為不再依賴於Cookie,所以你就不需要考慮對CSRF(跨站請求偽造)的防范。
- 性能: 一次網絡往返時間(通過數據庫查詢session信息)總比做一次HMACSHA256計算 的Token驗證和解析要費時得多.
- 不需要為登錄頁面做特殊處理: 如果你使用Protractor 做功能測試的時候,不再需要為登錄頁面做特殊處理.
- 基於標准化:你的API可以采用標准化的 JSON Web Token (JWT). 這個標准已經存在多個后端庫(.NET, Ruby, Java,Python, PHP)和多家公司的支持(如:Firebase,Google, Microsoft).
基於JWT的Token認證機制實現
實施 Token 驗證的方法挺多的,還有一些標准規范,其中JSON Web Token(JWT)是一個非常輕巧的規范 。JWT 標准的 Token 有三個部分:
- header(頭部)
- payload(數據)
- signature(簽名)
中間用點分隔開,並且都會使用 Base64 編碼,所以真正的 Token 看起來像這樣:
eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJuaW5naGFvLm5ldCIsImV4cCI6IjE0Mzg5NTU0NDUiLCJuYW1lIjoid2FuZ2hhbyIsImFkbWluIjp0cnVlfQ.SwyHTEx_RQppr97g4J5lKXtabJecpejuef8AqKYMAJc
Header
每個 JWT token 里面都有一個 header,也就是頭部數據。里面包含了使用的算法,這個 JWT 是不是帶簽名的或者加密的。主要就是說明一下怎么處理這個 JWT token 。
頭部里包含的東西可能會根據 JWT 的類型有所變化,比如一個加密的 JWT 里面要包含使用的加密的算法。唯一在頭部里面要包含的是 alg 這個屬性,如果是加密的 JWT,這個屬性的值就是使用的簽名或者解密用的算法。如果是未加密的 JWT,這個屬性的值要設置成 none。
示例:
{ "alg": "HS256" }
意思是這個 JWT 用的算法是 HS256。上面的內容得用 base64url 的形式編碼一下,所以就變成這樣:
eyJhbGciOiJIUzI1NiJ9
Payload
Payload 里面是 Token 的具體內容,這些內容里面有一些是標准字段,你也可以添加其它需要的內容。下面是標准字段:
- iss:Issuer,發行者
- sub:Subject,主題
- aud:Audience,觀眾
- exp:Expiration time,過期時間
- nbf:Not before
- iat:Issued at,發行時間
- jti:JWT ID
比如下面這個 Payload ,用到了 iss 發行人,還有 exp 過期時間這兩個標准字段。另外還有兩個自定義的字段,一個是 name ,還有一個是 admin 。
{ "iss": "ninghao.net", "exp": "1438955445", "name": "wanghao", "admin": true }
使用 base64url 編碼以后就變成了這個樣子:
eyJpc3MiOiJuaW5naGFvLm5ldCIsImV4cCI6IjE0Mzg5NTU0NDUiLCJuYW1lIjoid2FuZ2hhbyIsImFkbWluIjp0cnVlfQ
Signature
JWT 的最后一部分是 Signature ,這部分內容有三個部分,先是用 Base64 編碼的 header.payload ,再用加密算法加密一下,加密的時候要放進去一個 Secret ,這個相當於是一個密碼,這個密碼秘密地存儲在服務端。
- header
- payload
- secret
const encodedString = base64UrlEncode(header) + "." + base64UrlEncode(payload); HMACSHA256(encodedString, 'secret');
處理完成以后看起來像這樣:
SwyHTEx_RQppr97g4J5lKXtabJecpejuef8AqKYMAJc
最后這個在服務端生成並且要發送給客戶端的 Token 看起來像這樣:
eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJuaW5naGFvLm5ldCIsImV4cCI6IjE0Mzg5NTU0NDUiLCJuYW1lIjoid2FuZ2hhbyIsImFkbWluIjp0cnVlfQ.SwyHTEx_RQppr97g4J5lKXtabJecpejuef8AqKYMAJc
客戶端收到這個 Token 以后把它存儲下來,下回向服務端發送請求的時候就帶着這個 Token 。服務端收到這個 Token ,然后進行驗證,通過以后就會返回給客戶端想要的資源。
JWT的JAVA實現
Java中對JWT的支持可以考慮使用JJWT開源庫;JJWT實現了JWT, JWS, JWE 和 JWA RFC規范;下面將簡單舉例說明其使用:
maven導入
<dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.6.0</version> </dependency>
創建和解析token
/** * 解析JWT * @param jsonWebToken * @param base64Security * @return */ public static Claims parseJWT(String jsonWebToken, String base64Security) { try { Claims claims = Jwts.parser() .setSigningKey(DatatypeConverter.parseBase64Binary(base64Security)) .parseClaimsJws(jsonWebToken).getBody(); return claims; } catch (Exception ex) { return null; } } /** * 創建JWT * @param no * @param userId * @param issuer * @param TTLMillis * @param base64Security * @return */ public static String createJWT(String no, String userId, String issuer, long TTLMillis, String base64Security) { SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256; long nowMillis = System.currentTimeMillis(); Date now = new Date(nowMillis); //生成簽名密鑰 byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary(base64Security); Key signingKey = new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm.getJcaName()); //添加構成JWT的參數 JwtBuilder builder = Jwts.builder().setHeaderParam("typ", "JWT") .claim("no", no) .setSubject(userId) .setIssuer(issuer) .signWith(signatureAlgorithm, signingKey); //添加Token過期時間 if (TTLMillis >= 0) { long expMillis = nowMillis + TTLMillis; Date exp = new Date(expMillis); builder.setExpiration(exp).setNotBefore(now); } //生成JWT return builder.compact(); }
參考:
基於 Token 的身份驗證:JSON Web Token(附:Node.js 項目)
WEB后台--基於Token的WEB后台登錄認證機制(並講解其他認證機制以及cookie和session機制)