使用jwt實現restful介面使用者驗證
基於restful進行前後端分離的專案由於是基於json進行資料傳輸,前端既可以是web、android也可以是IOS,但是這樣就產生了一個問題,http協議是無狀態的,restful也是無狀態的,使用者狀態資訊該如何儲存?
restful介面的使用者認證有幾種方案:
1.使用session+cookie進行使用者認證。
2.使用token進行認證,如果使用者登陸成功,則通過某種規則(比如userName+timestamp)生成一個token,服務端將這個token快取起來,並設定失效時間,前端每次請求介面都通過http head帶著這個token,並在服務端進行驗證。
使用session存在的問題:
session和cookie是為了解決http無狀態的方案。session是使用者儲存在伺服器中的狀態資訊,cookie中則儲存jsessionId,請求伺服器時,伺服器讀取jsessionId從而確定使用者的身份資訊。
1.session+cookie用在restful介面上破壞了其“無狀態”的特性,session執行時都是儲存在記憶體中,而隨著認證使用者的增多,服務端的開銷會明顯增大。這也是restful最致力於通過“無狀態”解決的問題。如果使用session,那麼restful也就沒有什麼意義了。
2.session降低了系統擴充套件性。使用者認證之後,服務端做認證記錄,如果認證的記錄被儲存在記憶體中的話,這意味著使用者下次請求還必須要請求在這臺伺服器上,這樣才能拿到授權的資源,這樣在分散式的應用上,相應的限制了負載均衡器的能力。這也意味著限制了應用的擴充套件能力。
3.cookie不安全,很容易導致跨站請求偽造攻擊(CSRF)。
token存在的問題:
但這種方式仍然存在問題,比如如何確定token的過期時間?如果token的有效期過短,很容易造成使用者用著用著就掉線的情況,降低使用者體驗。但目前看來好像並沒有太好的辦法,只能儘量延長token的有效期,或者每隔一次前端自動向服務端請求一次token。
JWT(json web token)
Json web token (JWT), 是為了在網路應用環境間傳遞宣告而執行的一種基於JSON的開放標準((RFC 7519).該token被設計為緊湊且安全的,特別適用於分散式站點的單點登入(SSO)場景。JWT的宣告一般被用來在身份提供者和服務提供者間傳遞被認證的使用者身份資訊,以便於從資源伺服器獲取資源,也可以增加一些額外的其它業務邏輯所必須的宣告資訊,該token也可直接被用於認證,也可被加密。
JWT構成
JWT由三部分構成:
head:頭部
payload:負載資訊
signature:認證簽名
head
jwt的頭部由兩部分構成,一是jwt宣告,二是加密演算法宣告。
{
'typ': 'JWT',
'alg': 'HS256'
}
將上面的json進行base64編碼,即為jwt第一部分。
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
payload
顧名思義,payload用於儲存有效資訊。
iss: jwt簽發者
sub: jwt所面向的使用者
aud: 接收jwt的一方
exp: jwt的過期時間,這個過期時間必須要大於簽發時間
nbf: 定義在什麼時間之前,該jwt都是不可用的.
iat: jwt的簽發時間
jti: jwt的唯一身份標識,主要用來作為一次性token,從而回避重放攻擊。
signature
signature由上面兩部分組成,它需要base64加密後的header和base64加密後的payload連線組成的字串,然後通過header中宣告的加密方式進行加鹽secret組合加密,然後就構成了jwt的第三部分。其中的secret為私鑰,儲存在服務端。
使用:
將生成的jwt放在http請求頭上即可。
public class TokenUtil {
/**
* java io.jsonwebtoken框架
*/
public static SecretKey generalKey() {
/**
*生成私鑰
*/
byte[] encodedKey = new byte[0];
encodedKey = Base64Util.decode(Constant.JWT_SECERT);
SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
return key;
}
/**
* 簽發JWT
*
* @param id
* @param subject
* @param ttlMillis 有效時間
* @return
* @throws Exception
*/
public static String createJWT(String id, String subject, long ttlMillis) {
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
SecretKey secretKey = generalKey();
JwtBuilder builder = Jwts.builder()
.setId(id)
//簽發使用者
.setSubject(subject)
//簽發時間
.setIssuedAt(now)
//簽名演算法,私鑰
.signWith(signatureAlgorithm, secretKey);
//設定失效時間
if (ttlMillis >= 0) {
long expMillis = nowMillis + ttlMillis;
Date expDate = new Date(expMillis);
builder.setExpiration(expDate);
}
return builder.compact();
}
/**
* 驗證JWT
*
* @param jwtStr
* @return
*/
public static ResultInfo validateJWT(String jwtStr) {
ResultInfo checkResult = null;
Claims claims = null;
try {
claims = parseJWT(jwtStr);
checkResult = new ResultInfo(-100000);
} catch (ExpiredJwtException e) {
checkResult = new ResultInfo(-500);
} catch (SignatureException e) {
checkResult = new ResultInfo(-501);
} catch (Exception e) {
checkResult = new ResultInfo(-501);
}
return checkResult;
}
/**
* 解析JWT字串
*
* @param jwt
* @return
* @throws Exception
*/
public static Claims parseJWT(String jwt) throws Exception {
SecretKey secretKey = generalKey();
return Jwts.parser()
.setSigningKey(secretKey)
.parseClaimsJws(jwt)
.getBody();
}
}