Implementation principle of JSON Web Tokens

  • 2020-05-27 06:23:59
  • OfStack

preface

Recently, I was engaged in the transformation of an Python project and reconstructed the python project into Java project. I encountered this knowledge point in the process and found it quite practical. Therefore, I came back after work and wrote this summary while the iron is hot.

1. Introduction of advantages

JSON Web Tokens, jwt for short, is a security policy for the rest interface. There are many advantages in itself:

Solve cross-domain problems: this Token-based access policy can overcome the cross-domain problems of cookies.

The server is stateless and can scale horizontally. Token can be authenticated without having to store Session.

System decoupling, Token carries all user information, does not need to bind a specific authentication scheme, only need to know the encryption method and key can be encrypted and decrypted, which is conducive to decoupling.

Prevent cross-site scripting attacks without cookie technology and without the security of cross-site requests.

2. Principle introduction

The format of JSON Web Tokens is composed. jwt is a sequence of characters encoded by base64, separated by dots. jwt consists of three parts: the header header, the body playload and the signature sign.

1. The header Header of jwt is in json format:


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

Where typ is short for type, representing that the type is JWT, the encryption declaration is HS256, and exp represents the current time.

2.jwt's message body, Playload


{
  "userid":"123456",
  "iss":"companyName"
}

The specific fields of the message body can be defined and added according to the needs of the business, just take the key value of the field to get value when decrypting.

3. Generation of signature sign

Finally the signature, the signature generation is put header and playload respectively using base64url code, then use the '. 'take two encoded string together, put this together string matching key HMAC SHA - 256 encryption algorithm, finally base64 code again, it's got the signature sign. Finally the header and playload and sign with'. 'to connect to generate the whole JWT.

3. Introduction to calibration

The whole jwt structure is composed of the connection of header.playload.sign, only sign is encrypted with the key, and all information can be directly obtained in header and playload. The function of sign is only to verify whether the information of header and playload has been tampered with, so jwt cannot protect the data.

1. The encryption

For example, the field to be encrypted is userid. First, assemble json header header and message body playload in the previous format, and form a string according to header.playload. Then, encrypt JSON.playload according to the key and HS256 to obtain sign signature, and finally obtain jwtToken to be header.playload.sign.

2. The decryption

The back-end service verifies whether jwtToken has access to the interface service and performs decryption authentication, such as verifying the visitor's userid, first

To get header and playload and sign, divide the string by the. Sign into three strings. Then the header.playload assembly is encrypted with the key and the HAMC SHA-256 algorithm, and then a new string is obtained and compared with sign. If the first sample means that the data has not been tampered with, then exp is taken out from the header to judge the survival. If the survival exceeds, an empty string is returned, and the value of userid is returned during the survival period.

4. Code examples

1. Encryption and decryption of python code


#!/usr/bin/env python
# coding: utf-8

from itsdangerous import BadTimeSignature, SignatureExpired
from itsdangerous import TimedJSONWebSignatureSerializer as Serializer

APP_SECRET_KEY="secret"
MAX_TOKEN_AGE=1800
token_generator = Serializer(APP_SECRET_KEY, expires_in=MAX_TOKEN_AGE)

def generate_auth_token(userid):
  access_token = token_generator.dumps({"userid":userid})
  return access_token
def verify_token(token):
  try:
    user_auth = token_generator.loads(token)
    print type(token_generator)
  except SignatureExpired as e:
    raise e
  except BadTimeSignature as e:
    raise e
  return user_auth

2. Encryption and decryption of java code


package api.test.util;

import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;

import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;

import org.apache.commons.codec.binary.Base64;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import lombok.extern.slf4j.Slf4j;
import net.sf.json.JSONObject;

/**
 * jwt Encryption and decryption implementation 
 * 
 * @author zhengsc
 */
@Slf4j
public class TokenUtil {

  private String ISSUER = "companyName"; //  institutions 

  private String APP_SECRET_KEY = "secret"; //  The key 

  private long MAX_TOKEN_AGE = 1800; //  Survival time 

  /**
   *  generate userId the accessToken
   * 
   * @param userid
   * @return
   */
  public String generateAccessToken(String userid) {
    JSONObject claims = new JSONObject();
    claims.put("iss", ISSUER);
    claims.put("userid", userid);
    String accessToken = sign(claims, APP_SECRET_KEY);
    return accessToken;
  }

  /**
   *  Decryption program return userid
   * 
   * @param token
   * @return
   */
  public String verifyToken(String token) {
    String userid = "";
    try {
      String[] splitStr = token.split("\\.");
      String headerAndClaimsStr = splitStr[0] + "." +splitStr[1];
      String veryStr = signHmac256(headerAndClaimsStr, APP_SECRET_KEY);
      //  Verify that the data has been tampered with 
      if (veryStr.equals(splitStr[2])) {
        String header = new String(Base64.decodeBase64(splitStr[0]),"UTF-8");
        JSONObject head = JSONObject.fromObject(header);
        long expire = head.getLong("exp") * 1000L;
        long currentTime = System.currentTimeMillis();
        if (currentTime <= expire){ //  validation accessToken The validity of the 
          String claims = new String(Base64.decodeBase64(splitStr[1]),"UTF-8");
          JSONObject claim = JSONObject.fromObject(claims);
          userid = (String) claim.get("userid");
        }
      }
    } catch (UnsupportedEncodingException e) {
      log.error(e.getMessage(), e);
    }

    return userid;
  }

  /**
   *  Assemble encryption results jwt return 
   * 
   * @param claims
   * @param appSecretKey
   * @return
   */
  private String sign(JSONObject claims, String appSecretKey) {
    String headerAndClaimsStr = getHeaderAndClaimsStr(claims);
    String signed256 = signHmac256(headerAndClaimsStr, appSecretKey);
    return headerAndClaimsStr + "." + signed256;
  }

  /**
   *  Splice request headers and declarations 
   * 
   * @param claims
   * @return
   */
  private String getHeaderAndClaimsStr(JSONObject claims) {
    JSONObject header = new JSONObject();
    header.put("alg", "HS256");
    header.put("typ", "JWT");
    header.put("exp", System.currentTimeMillis() + MAX_TOKEN_AGE * 1000L);
    String headerStr = header.toString();
    String claimsStr = claims.toString();
    String headerAndClaimsStr = Base64.encodeBase64URLSafeString(headerStr.getBytes()) + "."
        + Base64.encodeBase64URLSafeString(claimsStr.getBytes());
    return headerAndClaimsStr;
  }

  /**
   *  will headerAndClaimsStr with SHA1 Encryption for sign
   * 
   * @param headerAndClaimsStr
   * @param appSecretKey
   * @return
   */
  private String signHmac256(String headerAndClaimsStr, String appSecretKey) {
    SecretKey key = new SecretKeySpec(appSecretKey.getBytes(), "HmacSHA256");
    String result = null;
    try {
      Mac mac;
      mac = Mac.getInstance(key.getAlgorithm());
      mac.init(key);
      result = Base64.encodeBase64URLSafeString(mac.doFinal(headerAndClaimsStr.getBytes()));
    } catch (NoSuchAlgorithmException | InvalidKeyException e) {
      log.error(e.getMessage(), e);
    }
    return result;
  }

}

Related articles: