基於JWT的Token認證機制(一)
阿新 • • 發佈:2018-12-25
簡介
JSON Web Token(JWT)是一個非常輕巧的規範,這個規範允許我們使用JWT在使用者和伺服器之間傳遞安全可靠的資訊。它是基於RFC 7519標準定義的一種可以安全傳輸的小巧和自包含的JSON物件。由於資料是使用數字簽名的,所以是可信任的和安全的。JWT可以使用HMAC演算法對secret進行加密或者使用RSA的公鑰私鑰對其進行簽名。
JWT的組成
一個JWT實際上就是一個字串,它由三部分組成,頭部(Header)、載荷(Payload)與簽名(Signature)。 拼接形式為: Header.Payload.Signature頭部(Header)
JWT的頭部使用者描述關於該JWT的最基本資訊,例如其型別以及簽名所使用的演算法等。這個可以被表示成一個JSON物件,比如下面型別就是JWT,使用的演算法是HS256。
{
"typ": "JWT",
"alg": "HS256"
}
上面的內容用Base64進行編碼,編碼後的字串如下:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
載荷(Payload)
Payload裡面是Token的具體內容,也就是Token的資料宣告(Claim),這些內容裡面有一些是標準欄位,也可以新增其他需要新增的內容,如userId,email等。{
"iss": "why",
"iat": 1416797419,
"exp": 1448333419,
"aud": "www.example.com",
"sub": "taobao.com",
}
- iss: 該JWT的簽發者,是否使用是可選的;
- sub: 該JWT所面向的使用者,是否使用是可選的;
- aud: 接收該JWT的一方,是否使用是可選的;
- exp(expires): 什麼時候過期,這裡是一個Unix時間戳,是否使用是可選的;
- iat(issued at): 在什麼時候簽發的(UNIX時間),是否使用是可選的;
- nbf (Not Before):如果當前時間在nbf裡的時間之前,則Token不被接受;一般都會留一些餘地,比如幾分鐘;,是否使用是可選的;
ewogICJpc3MiOiAid2h5IiwgCiAgImlhdCI6IDE0MTY3OTc0MTksIAogICJleHAiOiAxNDQ4MzMzNDE5LCAKICAiYXVkIjogInd
3dy5leGFtcGxlLmNvbSIsIAogICJzdWIiOiAidGFvYmFvLmNvbSIsIAp9
簽名(Signature)
簽名就是對頭部及載荷內容進行簽名,如果有人截取了Token資訊並對Token資訊進行修改,再進行編碼的話,那麼新的頭部和載荷的簽名和之前的簽名就將是不一樣的。而且,如果不知道伺服器加密時用的金鑰的話,得出來的簽名也一定是不一樣的。 簽名的過程是這樣的:採用header中宣告的演算法,接受三個引數:base64編碼的header、base64編碼的payload和祕鑰(secret)進行運算。簽名這一部分如果你願意的話,可以採用RSASHA256的方式進行公鑰、私鑰對的方式進行。在下面的程式碼中,我們將介紹使用的簽名方式。將上面的兩個編碼後的字串都用句號連線在一起(頭部在前),就形成了:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.ewogICJpc3MiOiAid2h5IiwgCiAgImlhdCI6IDE0MTY3OTc0MTksIAogICJleHAiOiAxNDQ4MzMzNDE5LCAKICAiYXVkIjogInd
3dy5leGFtcGxlLmNvbSIsIAogICJzdWIiOiAidGFvYmFvLmNvbSIsIAp9
我們將上面拼接完的字串用HS256演算法進行加密,在加密的時候,提供一個金鑰(secret),然後對上面的字串進行加密後得到簽名6OcLdX38eKWn1gCyJ6RNwsAvIvXxYq1CGBkFWgiTsyc
最後,將將這一部分也用句號拼接在字串後面,就形成了eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.ewogICJpc3MiOiAid2h5IiwgCiAgImlhdCI6IDE0MTY3OTc0MTksIAogICJleHAiOiAxNDQ4MzMzNDE5LCAKICAiYXVkIjogInd
3dy5leGFtcGxlLmNvbSIsIAogICJzdWIiOiAidGFvYmFvLmNvbSIsIAp9.6OcLdX38eKWn1gCyJ6RNwsAvIvXxYq1CGBkFWgiTsyc
JWT的工作流程
如上圖所示: 1. 使用者導航到登入頁,輸入使用者名稱和密碼,進行登入 2. 伺服器對登入使用者進行認證,如果認證通過,根據使用者的資訊和JWT的生成規則生成JWT Token 3. 伺服器將該Token字串返回 4. 客戶端得到Token資訊,將Token儲存在localStorage、sessionStorage或cookie等儲存形式中。 5. 當用戶請求伺服器API時,在請求的Header中加入 Authorization:Token。 6. 服務端對此Token進行校驗,如果合法就解析其中內容,根據其擁有的許可權和自己的業務邏輯給出響應結果,如果不通過,返回HTTP 401。 7. 使用者進入系統,獲得請求資源
JWT的JAVA實現
1.JWT的組成已經在上面詳細的介紹過了,所以如果自己寫生成方法的話也是可以的,就是先將Header資訊和Payload資訊分別進行Base64編碼,用英文句號隔開,然後用HMACSHA256演算法進行簽名,再把簽名組合起來的過程。 2. 這裡我們重點說下使用JJWT的開源庫;JJWT實現了JWT、JWS、JWE和JWA RFC規範,下面將簡單舉例說明如何使用: 生成Token碼package why.test;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;
import java.security.Key;
import java.util.Date;
/**
* @Author: 王洪玉
* @Decsription: 生成Token的工具類
* @Create: 2017/11/12 20:06
* @Modified By:
*/
public class TokenUtil {
private static final String APP_KEY = "why_key"; //進行數字簽名的私鑰,一定要保管好,不能和我一樣寫到部落格中。。。。。
private TokenUtil(){
}
/**
* 一個JWT實際上就是一個字串,它由三部分組成,頭部(Header)、載荷(Payload)與簽名(Signature)
* @param id 當前使用者ID
* @param issuer 該JWT的簽發者,是否使用是可選的
* @param subject 該JWT所面向的使用者,是否使用是可選的
* @param ttlMillis 什麼時候過期,這裡是一個Unix時間戳,是否使用是可選的
* @param audience 接收該JWT的一方,是否使用是可選的
* @return
*/
public static String createJWT(String id,String issuer,String subject,long ttlMillis, String audience){
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary(APP_KEY);
Key signingKey = new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm.getJcaName());
JwtBuilder jwtBuilder = Jwts.builder()
.setId(id)
.setSubject(subject)
.setIssuedAt(now)
.setIssuer(issuer)
.setAudience(audience)
.signWith(signatureAlgorithm,signingKey);
//設定Token的過期時間
if(ttlMillis >=0){
long expMillis = nowMillis + ttlMillis;
Date exp = new Date(expMillis);
jwtBuilder.setExpiration(exp);
}
return jwtBuilder.compact();
}
//私鑰解密token資訊
public static Claims getClaims(String jwt) {
return Jwts.parser()
.setSigningKey(DatatypeConverter.parseBase64Binary(APP_KEY))
.parseClaimsJws(jwt).getBody();
}
}
生成Token資訊並解密
@Test
public void testCreateToken(){
String userId = "WKSH121321KKsdfk";
String issuer = "http://whytfjybj.com";
String subject = "師範學院";
long ttlMillis = 1000 * 60;
String audience = "schoolNo";
String token = TokenUtil.createJWT(userId,issuer,subject,ttlMillis,audience);
//打印出token資訊
System.out.println(token);
// 解密token資訊
Claims claims = TokenUtil.getClaims(token);
System.out.println("---------------------------解密的token資訊----------------------------------");
System.out.println("ID: " + claims.getId());
System.out.println("Subject: " + claims.getSubject());
System.out.println("Issuer: " + claims.getIssuer());
System.out.println("Expiration: " + claims.getExpiration());
}
總結:JWT本身的實現非常簡單,雖然很高效的完成了使用者認證,但網站的安全性問題是一個非常重要且關鍵的問題,通過上面的JWT生成過程我們可以很清楚的知道,JWT本身沒有做加密處理,都是通過Base64進行編碼後方便通過HTTP傳輸而已,Header和Payload資訊都是可以通過Base64解碼進行檢視的。簡單的安全措施是: 1. 為保證使用者密、密碼驗證過程的安全性,敏感資訊需要在網路中傳輸,Token資訊也會在Request請求資訊中看到,因此,這個過程建議將網站進行SSL加密傳輸,採用HTTPS協議,以確保通道的安全性。 2.在Payload中儘量少帶敏感性資訊,只帶ID等無關網站安全的資訊。 總之,網站安全又是另一個非常重要的話題了,涉及到的方方面面都很廣,要防範的攻擊也很多,我們就不做討論了。