JSON Web Tokens(JWT)

如今API愈來愈流行,如何安全保護這些API? JSON Web Tokens(JWT)能提供基於JSON格式的安全認證。它有如下特色:html

  • JWT是跨不一樣語言的,JWT能夠在 .NET, Python, Node.js, Java, PHP, Ruby, Go, JavaScript和Haskell中使用
  • JWT是自我包涵的,它們包含了必要的全部信息,這就意味着JWT可以傳遞關於它本身的基本信息,好比用戶信息和簽名等。
  • JWT傳遞是容易的,由於JWT是自我包涵,它們能被完美用在HTTP頭部中,當須要受權API時,你只要經過URL一塊兒傳送它既可。

JWT易於辨識,是三段由小數點組成的字符串:java

aaaaaaaaaa.bbbbbbbbbbb.ccccccccccccgit

這三部分含義分別是header,payload, signaturegithub

Header

頭部包含了兩個方面:類型和使用的哈希算法(如HMAC SHA256):web

{
"typ": "JWT",
"alg": "HS256" 
}

 

對這個JSON字符進行base64encode編碼,咱們就有了首個JWT:算法

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9json

Payload

JWT的第二部分是payload,也稱爲 JWT Claims,這裏放置的是咱們須要傳輸的信息,有多個項目如註冊的claim名稱,公共claim名稱和私有claim名稱。api

註冊claim名稱有下面幾個部分:安全

  • iss: token的發行者
  • sub: token的題目
  • aud: token的客戶
  • exp: 常常使用的,以數字時間定義失效期,也就是當前時間之後的某個時間本token失效。
  • nbf: 定義在此時間以前,JWT不會接受處理。開始生效時間
  • iat: JWT發佈時間,能用於決定JWT年齡
  • jti: JWT惟一標識. 能用於防止 JWT重複使用,一次只用一個token;若是簽發的時候這個claim的值是「1」,驗證的時候若是這個claim的值不是「1」就屬於驗證失敗

 

公共claim名稱用於定義咱們本身創造的信息,好比用戶信息和其餘重要信息。服務器

私有claim名稱用於發佈者和消費者都贊成以私有的方式使用claim名稱。

下面是JWT的一個案例:

{
"iss": "scotch.io",
"exp": 1300819380,
"name": "Chris Sevilleja",
"admin": true 
}

 

簽名

JWT第三部分最後是簽名,簽名由如下組件組成:

  • header
  • payload
  • 密鑰

下面是咱們如何獲得JWT的第三部分:

var encodedString = base64UrlEncode(header) + "." + base64UrlEncode(payload); HMACSHA256(encodedString, 'secret');

這裏的secret是被服務器簽名,咱們服務器可以驗證存在的token並簽名新的token。

 

TWT支持的算法有:

                                 

============================================================================================================

以上是官網的理論部分,下面會有提供一些實例:

 首先 導入 依賴:

<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>3.2.0</version>
</dependency>

 1, 指定加密算法:

//HMAC
Algorithm algorithmHS = Algorithm.HMAC256("secret");

-------------------------------------------------------------------------
//RSA

Map<String,Object> keys=RSAUtils.getKeys();
RSAPublicKey publicKey = (RSAPublicKey)keys.get("public"); //Get the key instance
RSAPrivateKey privateKey = (RSAPrivateKey)keys.get("private");//Get the key instance
Algorithm algorithmRS = Algorithm.RSA256(publicKey, privateKey);

 2 , 生成token

  用HS256生成token

try {
    Algorithm algorithm = Algorithm.HMAC256("secret");
    String token = JWT.create()
        .withIssuer("auth0")
        .sign(algorithm);
} catch (UnsupportedEncodingException exception){
    //UTF-8 encoding not supported
} catch (JWTCreationException exception){
    //Invalid Signing configuration / Couldn't convert Claims.
}

  用RS256生成token

 Map<String,Object> keys=RSAUtils.getKeys();
        RSAPublicKey publicKey = (RSAPublicKey)keys.get("public"); //Get the key instance
        RSAPrivateKey privateKey = (RSAPrivateKey)keys.get("private");//Get the key instance
try {
    Algorithm algorithm = Algorithm.RSA256(publicKey, privateKey);
    String token = JWT.create()
        .withIssuer("auth0")
        .sign(algorithm);
} catch (JWTCreationException exception){
    //Invalid Signing configuration / Couldn't convert Claims.
}

  3, 驗證token

    1)普通驗證

       用HS256驗證token

String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE";
try {
    Algorithm algorithm = Algorithm.HMAC256("secret");
    JWTVerifier verifier = JWT.require(algorithm)
        .withIssuer("auth0")
        .build(); //Reusable verifier instance
    DecodedJWT jwt = verifier.verify(token);
} catch (UnsupportedEncodingException exception){
    //UTF-8 encoding not supported
} catch (JWTVerificationException exception){
    //Invalid signature/claims
}

     用RS256驗證token

String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE";
RSAPublicKey publicKey = //Get the key instance
RSAPrivateKey privateKey = //Get the key instance
try {
    Algorithm algorithm = Algorithm.RSA256(publicKey, privateKey);
    JWTVerifier verifier = JWT.require(algorithm)
        .withIssuer("auth0")
        .build(); //Reusable verifier instance
    DecodedJWT jwt = verifier.verify(token);
} catch (JWTVerificationException exception){
    //Invalid signature/claims
}

  2)在payLoad 是能夠自定義數據,用於驗證,包括時間等。

     在生成token的時候指定數據:

@Test
   public void gen1() throws IOException {
        String token ="";
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        //日期轉字符串
            Calendar calendar = Calendar.getInstance();
            calendar.add(Calendar.SECOND,30 ); //特定時間的年後
            Date date = calendar.getTime();
        try {
           Algorithm algorithm = Algorithm.HMAC256("mysecrite");
            token = JWT.create()
                   .withIssuer("auth0")
                    .withSubject("xiaoming")
                    .withClaim("name", 123)
                    .withArrayClaim("array", new Integer[]{1, 2, 3})
                    .withExpiresAt(date)
                   .sign(algorithm);
           System.out.println("loglogagel:"+token);
       } catch (UnsupportedEncodingException exception){
           //UTF-8 encoding not supported
       } catch (JWTCreationException exception){
           //Invalid Signing configuration / Couldn't convert Claims.
       }


   }

    驗證token是否過時,是否有制定的

@Test
  public void gen3(){
      String token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJhdXRoMCIsImV4cCI6MTQ5NzY4NTQwOX0.DHY-90JAA63_TvI-gRZ2oHCIItMajb45zB1tdCHQ_NQ";
      try {
          Algorithm algorithm = Algorithm.HMAC256("mysecrite");

          JWTVerifier.BaseVerification verification = (JWTVerifier.BaseVerification) JWT.require(algorithm)
                  .withIssuer("auth0")
                  .withSubject("xiaomong");
          Clock clock = new Clock() {
              @Override
              public Date getToday() {
                  return new Date();
              }
          };//Must implement Clock interface
          JWTVerifier verifier = verification.build(clock);
          DecodedJWT jwt = verifier.verify(token);
          System.out.println(jwt.getAlgorithm());
          System.out.println(jwt.getType());
          System.out.println(jwt.getIssuer());
          System.out.println(jwt.getExpiresAt());
      } catch (UnsupportedEncodingException exception){
          //UTF-8 encoding not supported
          exception.printStackTrace();
      } catch (JWTVerificationException exception){
          //Invalid signature/claims
          exception.printStackTrace();
      }
}

若是 subject驗證的不一致,就會報以下錯誤:

  

若是時間超過 30 秒,會報以下錯誤:

 對驗證的方法稍加修改:

 @Test
  public void gen3(){
      String token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ4aWFvbWluZyIsImFycmF5IjpbMSwyLDNdLCJpc3MiOiJhdXRoMCIsIm5hbWUiOiJJYW0gcmlnaHQgZnJvbSBjbGFpbSIsImV4cCI6MTQ5NzY4OTQ4NX0.6lsXISVAgi8B2wAvaZq4tj-h9Pgd6GGaOYZLz_gPFMU";
      try {
          Algorithm algorithm = Algorithm.HMAC256("mysecrite");

          JWTVerifier.BaseVerification verification = (JWTVerifier.BaseVerification) JWT.require(algorithm)
                  .withIssuer("auth0")
                  .withSubject("xiaoming");
          Clock clock = new Clock() {
              @Override
              public Date getToday() {
                  return new Date();
              }
          };//Must implement Clock interface
          JWTVerifier verifier = verification.build(clock);
          DecodedJWT jwt = verifier.verify(token);
          Map<String, Claim> claims = jwt.getClaims();    //Key is the Claim name
          Claim claim = claims.get("name");
          System.out.println(claim.asString());    //打印出claim的值
          System.out.println(jwt.getAlgorithm());
          System.out.println(jwt.getType());
          System.out.println(jwt.getIssuer());
          System.out.println(jwt.getExpiresAt());
      } catch (UnsupportedEncodingException exception){
          //UTF-8 encoding not supported
          exception.printStackTrace();
      } catch (JWTVerificationException exception){
          //Invalid signature/claims
          exception.printStackTrace();
      }

驗證後的最後結果:

 

      

4,claim的添加,獲取

    1) 內置的payload主要有如下幾個,若是沒有就返回null

     Issuer ("iss") :發佈者

String issuer = jwt.getIssuer();

Subject ("sub")

String subject = jwt.getSubject();

Audience ("aud")

List<String> audience = jwt.getAudience();

Expiration Time ("exp")

Date expiresAt = jwt.getExpiresAt();

Not Before ("nbf")

Date notBefore = jwt.getNotBefore();

Issued At ("iat")

Date issuedAt = jwt.getIssuedAt();

JWT ID ("jti")

String id = jwt.getId();

 

 2)定義私有的claim

    添加:

String token = JWT.create()
        .withClaim("name", 123)
        .withArrayClaim("array", new Integer[]{1, 2, 3})
        .sign(algorithm);

    獲取:

JWTVerifier verifier = JWT.require(algorithm)
    .withClaim("name", 123)
    .withArrayClaim("array", 1, 2, 3)
    .build();
DecodedJWT jwt = verifier.verify("my.jwt.token");

 目前,官方支持claim的類型的有:Boolean, Integer, Double, String, Date , String[] 和 Integer.

 

  5,  Header Claims

     1)header claims 是定義header部分的內容,基本都是默認定義,不須要本身去設置的,內置的有:

Algorithm ("alg")

String algorithm = jwt.getAlgorithm();

Type ("typ")

String type = jwt.getType();

Content Type ("cty")

String contentType = jwt.getContentType();

Key Id ("kid")

String keyId = jwt.getKeyId();

 

      2)添加:

Map<String, Object> headerClaims = new HashMap();
headerClaims.put("owner", "auth0");
String token = JWT.create()
        .withHeader(headerClaims)
        .sign(algorithm);

      3)獲取:

Claim claim = jwt.getHeaderClaim("owner");

 

總結: 看了其餘人的一些博客,發現他們的api都是相對老一點的版本,生成token是一步一步來,新的確實簡單方便不少。分享就這裏,歡迎交流。

補充參考連接:

web 中使用jwt:    https://github.com/jwtk/jjwt

 

參考地址:https://github.com/auth0/java-jwt

相關文章
相關標籤/搜索