1. 程式人生 > >基於JWT的Token認證機制(一)

基於JWT的Token認證機制(一)

簡介

          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不被接受;一般都會留一些餘地,比如幾分鐘;,是否使用是可選的;
 上面的json內容,經過Base64編碼後就成為如下內容:
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等無關網站安全的資訊。 總之,網站安全又是另一個非常重要的話題了,涉及到的方方面面都很廣,要防範的攻擊也很多,我們就不做討論了。