通常情況下葱跋,把API直接暴露出去是風險很大的澳化,不說別的崔步,直接被機器攻擊就喝一壺的。那么一般來說缎谷,對API要劃分出一定的權限級別井濒,然后做一個用戶的鑒權,依據(jù)鑒權結(jié)果給予用戶開放對應的API列林。目前瑞你,比較主流的方案有幾種:
- 用戶名和密碼鑒權,使用Session保存用戶鑒權結(jié)果希痴。
- 使用OAuth進行鑒權(其實OAuth也是一種基于Token的鑒權者甲,只是沒有規(guī)定Token的生成方式)
- 自行采用Token進行鑒權
第一種就不介紹了,由于依賴Session來維護狀態(tài)砌创,也不太適合移動時代虏缸,新的項目就不要采用了。第二種OAuth的方案和JWT都是基于Token的纺铭,但OAuth其實對于不做開放平臺的公司有些過于復雜寇钉。我們主要介紹第三種:JWT。
什么是JWT舶赔?
JWT是 Json Web Token
的縮寫。它是基于 RFC 7519 標準定義的一種可以安全傳輸?shù)?小巧 和 自包含 的JSON對象谦秧。由于數(shù)據(jù)是使用數(shù)字簽名的竟纳,所以是可信任的和安全的。JWT可以使用HMAC算法對secret進行加密或者使用RSA的公鑰私鑰對來進行簽名疚鲤。
JWT的工作流程
下面是一個JWT的工作流程圖锥累。模擬一下實際的流程是這樣的(假設受保護的API在/protected
中)
- 用戶導航到登錄頁,輸入用戶名集歇、密碼桶略,進行登錄
- 服務器驗證登錄鑒權,如果改用戶合法,根據(jù)用戶的信息和服務器的規(guī)則生成JWT Token
- 服務器將該token以json形式返回(不一定要json形式际歼,這里說的是一種常見的做法)
- 用戶得到token惶翻,存在localStorage、cookie或其它數(shù)據(jù)存儲形式中鹅心。
- 以后用戶請求
/protected
中的API時吕粗,在請求的header中加入Authorization: Bearer xxxx(token)
。此處注意token之前有一個7字符長度的Bearer
- 服務器端對此token進行檢驗旭愧,如果合法就解析其中內(nèi)容颅筋,根據(jù)其擁有的權限和自己的業(yè)務邏輯給出對應的響應結(jié)果。
- 用戶取得結(jié)果
為了更好的理解這個token是什么输枯,我們先來看一個token生成后的樣子议泵,下面那坨亂糟糟的就是了。
eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ3YW5nIiwiY3JlYXRlZCI6MTQ4OTA3OTk4MTM5MywiZXhwIjoxNDg5Njg0NzgxfQ.RC-BYCe_UZ2URtWddUpWXIp4NMsoeq2O6UF-8tVplqXY1-CI9u1-a-9DAAJGfNWkHE81mpnR3gXzfrBAB3WUAg復制代碼
但仔細看到的話還是可以看到這個token分成了三部分桃熄,每部分用 .
分隔肢簿,每段都是用 Base64 編碼的。如果我們用一個Base64的解碼器的話 ( www.base64decode.org/ )蜻拨,可以看到第一部分 eyJhbGciOiJIUzUxMiJ9
被解析成了:
{
"alg":"HS512"
}復制代碼
這是告訴我們HMAC采用HS512算法對JWT進行的簽名池充。
第二部分 eyJzdWIiOiJ3YW5nIiwiY3JlYXRlZCI6MTQ4OTA3OTk4MTM5MywiZXhwIjoxNDg5Njg0NzgxfQ
被解碼之后是
{
"sub":"wang",
"created":1489079981393,
"exp":1489684781
}復制代碼
這段告訴我們這個Token中含有的數(shù)據(jù)聲明(Claim),這個例子里面有三個聲明:sub
, created
和 exp
。在我們這個例子中,分別代表著用戶名英支、創(chuàng)建時間和過期時間敞贡,當然你可以把任意數(shù)據(jù)聲明在這里。
看到這里攀痊,你可能會想這是個什么鬼token,所有信息都透明啊,安全怎么保障咽瓷?別急,我們看看token的第三段 RC-BYCe_UZ2URtWddUpWXIp4NMsoeq2O6UF-8tVplqXY1-CI9u1-a-9DAAJGfNWkHE81mpnR3gXzfrBAB3WUAg
舰讹。同樣使用Base64解碼之后茅姜,咦,這是什么東東
D X ?DmYTe?L?UZcPZ0$gZAY?_7?wY@復制代碼
最后一段其實是簽名月匣,這個簽名必須知道秘鑰才能計算钻洒。這個也是JWT的安全保障。這里提一點注意事項锄开,由于數(shù)據(jù)聲明(Claim)是公開的素标,千萬不要把密碼等敏感字段放進去,否則就等于是公開給別人了萍悴。
也就是說JWT是由三段組成的头遭,按官方的叫法分別是header(頭)寓免、payload(負載)和signature(簽名):
header.payload.signature復制代碼
頭中的數(shù)據(jù)通常包含兩部分:一個是我們剛剛看到的 alg
,這個詞是 algorithm
的縮寫计维,就是指明算法袜香。另一個可以添加的字段是token的類型(按RFC 7519實現(xiàn)的token機制不只JWT一種),但如果我們采用的是JWT的話享潜,指定這個就多余了困鸥。
{
"alg": "HS512",
"typ": "JWT"
}復制代碼
payload中可以放置三類數(shù)據(jù):系統(tǒng)保留的、公共的和私有的:
- 系統(tǒng)保留的聲明(Reserved claims):這類聲明不是必須的剑按,但是是建議使用的疾就,包括:iss (簽發(fā)者), exp (過期時間),
sub (主題), aud (目標受眾)等。這里我們發(fā)現(xiàn)都用的縮寫的三個字符艺蝴,這是由于JWT的目標就是盡可能小巧猬腰。 - 公共聲明:這類聲明需要在 IANA JSON Web Token Registry 中定義或者提供一個URI,因為要避免重名等沖突猜敢。
- 私有聲明:這個就是你根據(jù)業(yè)務需要自己定義的數(shù)據(jù)了姑荷。
簽名的過程是這樣的:采用header中聲明的算法,接受三個參數(shù):base64編碼的header缩擂、base64編碼的payload和秘鑰(secret)進行運算鼠冕。簽名這一部分如果你愿意的話,可以采用RSASHA256的方式進行公鑰胯盯、私鑰對的方式進行懈费,如果安全性要求的高的話。
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)復制代碼
JWT的生成和解析
為了簡化我們的工作博脑,這里引入一個比較成熟的JWT類庫憎乙,叫 jjwt
( github.com/jwtk/jjwt )。這個類庫可以用于Java和Android的JWT token的生成和驗證叉趣。
JWT的生成可以使用下面這樣的代碼完成:
String generateToken(Map<String, Object> claims) {
return Jwts.builder()
.setClaims(claims)
.setExpiration(generateExpirationDate())
.signWith(SignatureAlgorithm.HS512, secret) //采用什么算法是可以自己選擇的泞边,不一定非要采用HS512
.compact();
}復制代碼
數(shù)據(jù)聲明(Claim)其實就是一個Map,比如我們想放入用戶名疗杉,可以簡單的創(chuàng)建一個Map然后put進去就可以了阵谚。
Map<String, Object> claims = new HashMap<>();
claims.put(CLAIM_KEY_USERNAME, username());復制代碼
解析也很簡單,利用 jjwt
提供的parser傳入秘鑰乡数,然后就可以解析token了椭蹄。
Claims getClaimsFromToken(String token) {
Claims claims;
try {
claims = Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody();
} catch (Exception e) {
claims = null;
}
return claims;
}復制代碼
JWT本身沒啥難度,但安全整體是一個比較復雜的事情净赴,JWT只不過提供了一種基于token的請求驗證機制。但我們的用戶權限罩润,對于API的權限劃分玖翅、資源的權限劃分,用戶的驗證等等都不是JWT負責的。也就是說金度,請求驗證后应媚,你是否有權限看對應的內(nèi)容是由你的用戶角色決定的。所以我們這里要利用Spring的一個子項目Spring Security來簡化我們的工作猜极。