1 常見的認證機制
1.1 HTTP Basic Auth
HTTP Basic Auth簡單點說明就是每次請求API時都提供用戶的 username 和 password,簡言之,Basic Auth 是配合RESTful API 使用的最簡單的認證方式,只需提供用戶名密碼即可,但由于有把用戶名密碼暴露給第三方客戶端的風險,在生產(chǎn)環(huán)境下被使用的越來越少。因此,在開發(fā)對外開放的RESTful API時,盡量避免采用HTTP Basic
Auth。
1.2 Cookie Auth
Cookie 認證機制就是為一次請求認證在服務端創(chuàng)建一個 Session 對象,同時在客戶端的瀏覽器端創(chuàng)建了一個Cookie 對象;通過客戶端帶上來 Cookie 對象來與服務器端的 session 對象匹配來實現(xiàn)狀態(tài)管理的。默認的,當我們關閉瀏覽器的時候,cookie 會被刪除。但可以通過修改cookie 的 expire time 使 cookie 在一定時間內(nèi)有效。

1.3 Oauth
OAuth(開放授權(quán))是一個開放的授權(quán)標準,允許用戶讓第三方應用訪問該用戶在某一 web 服務上存儲的私密的資源(如照片,視頻,聯(lián)系人列表),而無需將用戶名和密碼提供給第三方應用。
OAuth 允許用戶提供一個令牌,而不是用戶名和密碼來訪問他們存放在特定服務提供者的數(shù)據(jù)。每一個令牌授權(quán)一個特定的第三方系統(tǒng)(例如,視頻編輯網(wǎng)站)在特定的時段(例如,接下來的2小時內(nèi))內(nèi)訪問特定的資源(例如僅僅是某一相冊中的視頻)。這樣,OAuth讓用戶可以授權(quán)第三方網(wǎng)站訪問他們存儲在另外服務提供者的某些特定信息,而非所有內(nèi)容。

1.4 Token Auth
Token機制相對于Cookie 機制又有什么好處呢?
- 支持跨域訪問: Cookie 是不允許垮域訪問的,這一點對 Token 機制是不存在的,前提是傳輸?shù)挠脩粽J證信息通過 HTTP 頭傳輸.
- 無狀態(tài)(也稱:服務端可擴展行): Token 機制在服務端不需要存儲 session 信息,因為 Token 自身包含了所有登錄用戶的信息,只需要在客戶端的 cookie 或本地介質(zhì)存儲狀態(tài)信息.
- 更適用 CDN: 可以通過內(nèi)容分發(fā)網(wǎng)絡請求服務端的所有資料(如:javascript,HTML, 圖片等),而服務端只要提供 API 即可.
- 去耦: 不需要綁定到一個特定的身份驗證方案。Token 可以在任何地方生成,只要在你的 API 被調(diào)用的時候,你可以進行 Token 生成調(diào)用即可.
- 更適用于移動應用: 當你的客戶端是一個原生平臺(iOS, Android,Windows 8等)時,Cookie 是不被支持的(你需要通過Cookie容器進行處理),這時采用 Token 認證機制就會簡單得多。
- CSRF: 因為不再依賴于 Cookie,所以就不需要考慮對 CSRF(跨站請求偽造)的防范。
- 性能: 一次網(wǎng)絡往返時間(通過數(shù)據(jù)庫查詢 session 信息)總比做一次 HMACSHA256 計算的 Token 驗證和解析要費時得多
- 不需要為登錄頁面做特殊處理: 如果你使用 Protractor 做功能測試的時候,不再需要為登錄頁面做特殊處理
- 基于標準化: API 可以采用標準化的 JSON Web Token (JWT),這個標準已經(jīng)存在多個后端庫(.NET, Ruby, Java, Python, PHP)和多家公司的支持(如:Firebase,Google, Microsoft)。

2 基于JWT的Token認證機制實現(xiàn)
2.1 什么是 JWT?
JSON Web Token(JWT)是一個非常輕巧的規(guī)范,這個規(guī)范允許我們使用 JWT 在用
戶和服務器之間傳遞安全可靠的信息。
2.2 JWT 組成
一個 JWT 實際上就是一個字符串,由頭部、載荷和簽名組成
2.2.1 頭部
頭部用于描述 JWT 最基本的信息,例如其類型、簽名使用的壓縮算法等,它可以被表示為一個 json 對象:
{"tye": "JWT", "alg": "HS256"}
在頭部指定了簽名算法是 HS256 算法,我們進行 Base64 編碼,編碼后的字符串如下:
eyJ0eWUiOiAiSldUIiwgImFsZyI6ICJIUzI1NiJ9
2.2.2 載荷
載荷就是存放有效信息的地方
載荷(Payload)
{ "iss": "Online JWT Builder",
"iat": 1416797419,
"exp": 1448333419,
"aud": "www.example.com",
"sub": "djm@example.com",
"GivenName": "djm",
"Surname": "DJM",
"Email": "djm@example.com",
"Role": [ "Manager", "Project Administrator" ]
}
- iss: JWT 的簽發(fā)者,是否使用是可選的;
- sub: JWT 所面向的用戶,是否使用是可選的;
- aud: 接收 JWT 的一方,是否使用是可選的;
- exp(expires): 什么時候過期,這里是一個 Unix 時間戳,是否使用是可選的;
- iat(issued at): 在什么時候簽發(fā)的(UNIX時間),是否使用是可選的;
- nbf (Not Before):如果當前時間在 nbf 里的時間之前,則 Token 不被接受;一般都會留一些余地,比如幾分鐘, 是否使用是可選的;
將上面的 JSON 對象進行 base64 編碼可以得到下面的字符串,這個字符串我們將它稱作JWT的Payload
eyAiaXNzIjogIk9ubGluZSBKV1QgQnVpbGRlciIsIAogICJpYXQiOiAxNDE2Nzk3NDE5LCAKICAiZXhwIjogMTQ0ODMzMzQxOSwgCiAgImF1ZCI6ICJ3d3cuZXhhbXBsZS5jb20iLCAKICAic3ViIjogImRqbUBleGFtcGxlLmNvbSIsIAogICJHaXZlbk5hbWUiOiAiZGptIiwgCiAgIlN1cm5hbWUiOiAiREpNIiwgCiAgIkVtYWlsIjogImRqbUBleGFtcGxlLmNvbSIsIAogICJSb2xlIjogWyAiTWFuYWdlciIsICJQcm9qZWN0IEFkbWluaXN0cmF0b3IiIF0gCn0=
2.2.3 簽證
這個簽證信息由三部分組成:header(base64)、payload(base64)、secret,中間使用 . 連接,然后使用 header 聲明的的加密方式進行加言 secret 組合加密,就構(gòu)成了第三部分
3 JWT 實現(xiàn) JJWT
3.1 什么是 JJWT
JJWT 是一個提供端到端的JWT創(chuàng)建和驗證的Java庫。永遠免費和開源(Apache License,版本2.0),JJWT 很容易使用和理解。它被設計成一個以建筑為中心的流暢界面,隱藏了它的大部分復雜性。
3.2 JJWT 快速入門
3.2.1 token的創(chuàng)建
public String createJWT(String id, String subject, String roles) {
// 當前計算機時間
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
JwtBuilder builder = Jwts.builder()
.setId(id)
// 設置用戶
.setSubject(subject)
// 設置簽約時間
.setIssuedAt(now)
// 簽名和使用簽名壓縮算法、
.signWith(SignatureAlgorithm.HS256, key)
// 設置claims
.claim("roles", roles);
if (ttl > 0)
builder.setExpiration(new Date(nowMillis + ttl));
return builder.compact();
}
Jwts.builder() 返回了一個 DefaultJwtBuilder()
DefaultJwtBuilder 的屬性,如下:
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
private Header header; //頭部
private Claims claims; //聲明
private String payload; //載荷
private SignatureAlgorithm algorithm; //簽名算法
private Key key; //簽名key
private byte[] keyBytes; //簽名key的字節(jié)數(shù)組
private CompressionCodec compressionCodec; //壓縮算法
setHeader() 有兩種參數(shù)形式,一種是 Header 接口的實現(xiàn),一種是 Map, 其實 Header 接口也集成自 Map,如過以 Map 作為參數(shù),在 setHeader 的時候會生成默認的 Header 接口實現(xiàn) DefaultHeader。
如果不設置簽名,也不進行壓縮,alg 也應該存在,否則,對其解析就會報錯,在生成 jwt 時,如果不設置簽名,可將 alg 設置為 none
setPayload() 設置payload,直接賦值
setClaims() 設置claims,以參數(shù)創(chuàng)建一個新Claims對象,直接賦值
claim() 如果 builder 中 Claims 屬性為空,則創(chuàng)建 DefaultClaims 對象,并把鍵值放入;如果 Claims 屬性不為空,獲取之后判斷鍵值,存在則更新,不存在則直接放入。
當然也可以在Payload中添加一些自定義的屬性claims鍵值對
JJWT 還提供了 JWT標準7個保留聲明(Reserved claims)的設置方法,7個聲明都是可選的,也就是說可以不用設置。
setIssuer()
setSubject()
setAudience()
setExpiration()
setNotBefore()
setIssuedAt()
setId()
當然也可以在Payload中添加一些自定義的屬性claims鍵值對
compressWith() 壓縮方法。當載荷過長時可對其進行壓縮。可采用jjwt實現(xiàn)的兩種壓縮方法CompressionCodecs.GZIP、CompressionCodecs.DEFLATE
signWith() 簽名方法:兩個參數(shù)分別是簽名算法和自定義的簽名Key。簽名 key 可以 byte[] 、String 及 Key 的形式傳入,前兩種形式均存入 builder 的 keyBytes 屬性,后一種形式存入 builder 的 key 屬性,如果是第二種 key,則將其進行 base64 解碼獲得 byte[] 。
compact() 生成JWT
3.2.2 token 解析
public Claims parseJWT(String jwtStr) {
return Jwts.parser()
.setSigningKey(key)
.parseClaimsJws(jwtStr)
.getBody();
}