I have written an article about interface service specification before, and the original article here focuses on the security issue of obtaining tokens through AppID, AppKey, TIMESTAMP, nonce and sign, and using tokens to guarantee the security of interface service. Today we’ll talk about a more convenient way to generate tokens using JWT.

What is JWT

JSON Web Tokens (JWT) define a compact and self-contained way to securely transfer information between parties as JSON objects. This information can be verified and trusted because it is digitally signed. JWT can set the validity period.

JWT is a long string containing Header, Playload, and Signature. To separate.

Headers

The Headers section describes the basic information of JWT, usually including the signature algorithm and token type. The data is as follows:

{
    "alg": "RS256"."typ": "JWT"
}
Copy the code

Playload

Playload is where valid information is stored. JWT defines the following seven fields, which are recommended but not mandatory:

iss: JWT issued to
sub: The users JWT is aimed at
aud: The party receiving JWT
exp: Expiration time of JWT, which must be longer than the issuing time
nbf: When is the JWT defined to be unavailable
iat: The issuing time of JWT
jti: The unique identity of JWT is mainly used as a one-time token
Copy the code

In addition, we can also customize the content

{
    "name":"Java journey"."age":18
}
Copy the code

Signature

Signature is a string that encrypts the first two parts of JWT. Headers and Playload are base64 encoded and encrypted using the encryption algorithm and key specified in Headers to get the third part of JWT.

JWT generates and parses tokens

Introduce JWT dependencies in application services

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.0</version>
</dependency>
Copy the code

Generate a token encrypted using RSA algorithm and valid for 30 minutes according to the JWT definition

public static String createToken(User user) throws Exception{

    return Jwts.builder()
        .claim("name",user.getName())
        .claim("age",user.getAge())
        / / rsa encryption
        .signWith(SignatureAlgorithm.RS256, RsaUtil.getPrivateKey(PRIVATE_KEY))
        // Valid for 30 minutes
        .setExpiration(DateTime.now().plusSeconds(30 * 60).toDate())
        .compact();
}
Copy the code

After the login interface passes the authentication, it invokes JWT to generate a token with the user id to respond to the user. In the following request, the header carries the token for verification. After the verification, the application service is normally accessed.

public static Claims parseToken(String token) throws Exception{
    return Jwts
        .parser()
        .setSigningKey(RsaUtil.getPublicKey(PUBLIC_KEY))
        .parseClaimsJws(token)
        .getBody();
}
Copy the code

3. Token renewal

Above tells the story of JWT validation process, we consider such a problem, now the client carry token access interface, the token attestation by the client place the order successfully, return to place an order as a result, the client then with token calls payment interface of attestation to find token fails, then what should I do? You can only tell the user the token is invalid, and then let the user log in again to get the token? Oauth2 does a good job in this aspect. In addition to issuing tokens, it also issues refresh_tokens. When a token expires, it calls refresh_token to get the token again. Then prompt the user to log in. Now let’s emulate the oAuth2 implementation to complete the JWT refresh_token.

After a user logs in and issues a token, an encrypted string is generated as refresh_token. Refresh_token is stored in redis and set a reasonable expiration time. The token and refresh_token are then responded to the client. The pseudocode is as follows:

@PostMapping("getToken")
public ResultBean getToken(@RequestBody LoingUser user){

    ResultBean resultBean = new ResultBean();
    // User information verification failed, the response is incorrect
    if(! user){ resultBean.fillCode(401."The account password is not correct");
        return resultBean;
    }
    String token = null;
    String refresh_token = null;
    try {
        // Token generated by JWT
        token = JwtUtil.createToken(user);
        / / refresh token
        refresh_token = Md5Utils.hash(System.currentTimeMillis()+"");
        // Refresh_token expires in 24 hours
        redisUtils.set("refresh_token:"+refresh_token,token,30*24*60*60);
    } catch (Exception e) {
        e.printStackTrace();
    }

    Map<String,Object> map = new HashMap<>();
    map.put("access_token",token);
    map.put("refresh_token",refresh_token);
    map.put("expires_in".2*60*60);
    resultBean.fillInfo(map);
    return resultBean;
}
Copy the code

When a client invokes an interface, it carries a token in the request header and intercepts the request in the interceptor to verify the token validity. If the token verification fails, the client checks whether refresh_token is a request. If refresh_token verification also fails, the client responds with an authentication exception. The client is prompted to log in again with the following pseudocode:

HttpHeaders headers = request.getHeaders();
// Get the token in the request header
String token = headers.getFirst("Authorization");
// Determine whether there is a token in the request header
if (StringUtils.isEmpty(token)) {
    resultBean.fillCode(401."Authentication failed. Please bring a valid token.");
    return resultBean;
}
if(! token.contains("Bearer")){
    resultBean.fillCode(401."Authentication failed. Please bring a valid token.");
    return resultBean;
}

token = token.replace("Bearer "."");
// Resolve the token if there is one in the request header
try {
    Claims claims = TokenUtil.parseToken(token).getBody();
} catch (Exception e) {
    e.printStackTrace();
    String refreshToken = redisUtils.get("refresh_token:" + token)+"";
    if(StringUtils.isBlank(refreshToken) || "null".equals(refreshToken)){
        resultBean.fillCode(403."Refresh_token has expired. Please obtain the token again.");
        returnresultbean; }}Copy the code

Refresh_token is used to exchange tokens for tokens.

@PostMapping("refreshToken")
public Result refreshToken(String token){

    ResultBean resultBean = new ResultBean();
    String refreshToken = redisUtils.get(TokenConstants.REFRESHTOKEN + token)+"";
    String access_token = null;
    try {
        Claims claims = JwtUtil.parseToken(refreshToken);
        String username = claims.get("username") +"";
        String password = claims.get("password") +"";
        LoginUser loginUser = new LoginUser();
        loginUser.setUsername(username);
        loginUser.setPassword(password);
        access_token = JwtUtil.createToken(loginUser);
    } catch (Exception e) {
        e.printStackTrace();
    }
    Map<String,Object> map = new HashMap<>();
    map.put("access_token",access_token);
    map.put("refresh_token",token);
    map.put("expires_in".30*60);
    resultBean.fillInfo(map);
    return resultBean;
}
Copy the code

Through the above analysis, we have simply implemented the issues of token issuance, verification and renewal. As a lightweight authentication framework, JWT is very convenient to use, but there are also some problems.

  • The Playload part of JWT is only base64 encoded, so our information is actually completely exposed. It is generally not desirable to store sensitive information in JWT.

  • The token generated by JWT is relatively long, and the token is carried in the request header each time, resulting in larger request theft and certain performance problems.

  • After JWT is generated, the server cannot be discarded and can only wait for JWT to expire.

Here is a scenario I saw online that is applicable to JWT:

  • The validity of short

  • You only want to be used once

For example, users sign up and send an email asking them to activate their account, usually with a link that identifies the user, is time-sensitive (usually only allowed to be activated for a few hours), cannot be tampered with to activate other possible accounts, and is one-time. This is where JWT is appropriate.