Using Jwt to complete Token verification in Spring project

Posted by StathisG on Sun, 24 Oct 2021 15:07:25 +0200

  1, What is JWT? Why use JWT?

Json web token (JWT) is a JSON based open standard (RFC 7519) implemented to transfer declarations between network application environments
 The token is designed to be compact and secure, especially suitable for single sign on (SSO) scenarios of distributed sites.
The declaration of JWT is generally used to transfer the authenticated user identity information between identity providers and service providers, so as to obtain resources from the resource server. Some additional declaration information necessary for other business logic can also be added. The token can also be directly used for authentication or encrypted.

With the development of technology and the popularity of distributed web applications, the cost of managing user login status through session is becoming higher and higher. Therefore, it has gradually developed into a token method to verify login identity, and then use the token to retrieve the cached user information in redis. With the emergence of jwt later, the verification method is more simple and convenient. Instead of caching through redis, it is directly retrieved according to the token The saved user information and the verification of token availability make single sign on easier. here There is also an online JWT generator

2, When should you use JSON Web Tokens

It is useful to use JSON web tokens in the following scenarios:

  • Authorization   (authorization): This is the most common scenario for using JWT. Once the user logs in, each subsequent request will contain JWT, allowing the user to access the routes, services and resources allowed by the token. Single sign on is a feature of JWT, which is widely used now, because it has little overhead and can be easily used across domains.
  • Information Exchange   (information exchange): JSON Web Tokens is undoubtedly a good way to securely transfer information between parties. Because JWTs can be signed, for example, with a public / private key pair, you can determine that the sender is the person they say. In addition, since the signature is calculated using header and payload, you can also verify that the content has not been tampered with.

2, Structure of JSON Web Token

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkdhcnJ5IENoYW4iLCJpYXQiOjEyMzQ1Njc4OX0.nxzzGLyI93VexcBf_pVDmeOFYrYvZpvkEbQ-9oK9C2c

JSON Web Token consists of three parts, which are connected by dots (.)

  • Header
  • Payload
  • Signature

Therefore, a typical JWT looks like this:

xxxxx.yyyyy.zzzzz

Next, let's take a look at each part:

Header

The header typically consists of two parts: the type of token ("JWT") and the algorithm name (such as HMAC SHA256 or RSA, etc.).

For example:

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

Then, code the JSON with Base64 to get the first part of JWT

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9

Payload

The second part of JWT is payload, which contains declarations (requirements). Declarations are declarations about entities (usually users) and other data. There are three types of declarations: registered, public and private.

  • Registered claims registered in the standard: there are a set of predefined claims that are not mandatory but recommended, such as iss (issuer), exp (expiration time), sub (subject), aud (audience), etc.
  • Public claims: can be defined at will.
  • Private claims: private claims used to share information between parties who agree to use them and are not registered or public.

Declaration registered in the standard   (recommended but not mandatory):

  • iss: jwt issuer
  • Sub: the user JWT is targeting
  • aud: party receiving jwt
  • Exp: the expiration time of JWT, which must be greater than the issuing time
  • nbf: define the time before which the jwt is unavailable
  • IAT: issuing time of JWT
  • JTI: the unique identity of JWT, which is mainly used as a one-time token to avoid replay attacks.

Here is an example:

{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true
}

The second part of JWT is obtained by Base64 encoding the payload

eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkdhcnJ5IENoYW4iLCJpYXQiOjEyMzQ1Njc4OX0

Note that do not place sensitive information in JWT's payload or header unless they are encrypted.

Signature

In order to get the signature part, you must have an encoded header, an encoded payload and a secret key. The signature algorithm is the one specified in the header, and then sign them.

The secret key is stored on the server. The server will generate and verify the token based on this key, so it needs to be protected.

  • Header (after Base64)
  • Payload (after Base64)
  • secret

Use the above base64 encrypted header and base64 encrypted payload to form a string (with the header first), and then use the encryption method declared in the header for salt secret combination encryption to form the third part of jwt. For example:

HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)

nxzzGLyI93VexcBf_pVDmeOFYrYvZpvkEbQ-9oK9C2c

The last step of the signature process is actually to sign the header and load content. Generally speaking, the output generated by the encryption algorithm for different inputs is always different. For two different inputs, the probability of generating the same output is extremely small (possibly smaller than my probability of becoming the richest man in the world). Therefore, we put "Different inputs produce different outputs" should be regarded as an inevitable event.

Therefore, if someone decodes and modifies the contents of the header and payload and then encodes them, the signature of the new header and payload will be different from the previous signature. Moreover, if the key used by the server for encryption is not known, the resulting signature will be different.

After receiving the JWT, the server application will first re sign the contents of the header and payload with the same algorithm. How does the server application know which algorithm we use? Don't forget that we have indicated our encryption algorithm in the header of the JWT with the alg field.

If the server application signs the header and payload in the same way again and finds that the signature calculated by itself is different from the received signature, it means that the content of the Token has been moved by others. We should reject the Token and return an HTTP 401 Unauthorized response.

Signature is used to verify whether a message has been changed during delivery. For a token signed with a private key, it can also verify whether the sender of the JWT is what it calls the sender.

Note: in JWT, you should not add any sensitive data to the payload, such as the user's password.

You can see from a picture on the official website:

3, Learn about JSON Web Tokens and their workflow

Jwt workflow

  1. The application (or client) wants to authorize the server to request authorization. For example, if the authorization code process is used, it is / oauth/authorize
  2. After the authorization is granted, the authorization server returns an access token to the application
  3. Applications use access token to access protected resources (such as API)

Authorization is usually added to the request header and marked with Bearer:

fetch('api/user/1', {
  headers: {
    'Authorization': 'Bearer ' + token
  }
})

The server will verify the token, and if it passes the verification, it will return the corresponding resources.

Five understandings of Token authentication

  • A Token is a collection of information;
  • Include enough information in the Token to reduce the probability of querying the database in subsequent requests;
  • The server needs to check the Token information of the cookie and HTTP authorization header;
  • Based on the above point, you can use a set of token authentication code to face browser clients and non browser clients;
  • Because the token is signed, we can think that a token that can be decoded and authenticated is issued by our system, and the information carried is legal and valid;

4, How Jwt is used (code)

maven dependency to use

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>

The following is the code of method 1:

//Method of generating jwt
private String createJWT(String id, String issuer, String subject, long ttlMillis) {
 
    //The JWT signature algorithm we will be using to sign the token
    SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
    long nowMillis = System.currentTimeMillis();
    Date now = new Date(nowMillis);
 
    //We will sign our JWT with our ApiKey secret
    byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary(apiKey.getSecret());
    Key signingKey = new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm.getJcaName());
 
    //Let's set the JWT Claims
    JwtBuilder builder = Jwts.builder().setId(id)
                                .setIssuedAt(now)
                                .setSubject(subject)
                                .setIssuer(issuer)
                                .signWith(signatureAlgorithm, signingKey);
 
    //if it has been specified, let's add the expiration
    if (ttlMillis >= 0) {
        long expMillis = nowMillis + ttlMillis;
        Date exp = new Date(expMillis);
        builder.setExpiration(exp);
    }
    //Builds the JWT and serializes it to a compact, URL-safe string
    return builder.compact();
}

//*****************The following is the method of parsing jwt*****************//
//Sample method to validate and read the JWT
private void parseJWT(String jwt) {
    //This line will throw an exception if it is not a signed JWS (as expected)
    Claims claims = Jwts.parser()        
       .setSigningKey(DatatypeConverter.parseBase64Binary(apiKey.getSecret()))
       .parseClaimsJws(jwt).getBody();
    System.out.println("ID: " + claims.getId());
    System.out.println("Subject: " + claims.getSubject());
    System.out.println("Issuer: " + claims.getIssuer());
    System.out.println("Expiration: " + claims.getExpiration());
}

The following is the code of method 2:

    /**
     * secret key
     */
    private static final String SECRET = ".iloVeyOu@123";

    /**
     * Generate token from data claim
     *
     * @param claims Data declaration
     * @return token
     */
    private String generateToken(Map<String, Object> claims) {
        Date expirationDate = new Date(System.currentTimeMillis() + 2592000L * 1000);
        return Jwts.builder().setClaims(claims).setExpiration(expirationDate).signWith(SignatureAlgorithm.HS512, SECRET).compact();
    //The following is a declaration of using jwt to provide standards
    /**JwtBuilder builder = Jwts.builder().setId(id)
                                .setIssuedAt(now)
                                .setSubject(subject)
                                .setIssuer(issuer)
                                .signWith(signatureAlgorithm, signingKey);*/
    } 

    /**
     * Get data declaration from token
     *
     * @param token token
     * @return Data declaration
     */
    private Claims getClaimsFromToken(String token) {
        Claims claims;
        try {
            claims = Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token).getBody();
        } catch (Exception e) {
            claims = null;
        }
        return claims;
    }

    /**
     * Generate token
     *
     * @param userDetails user
     * @return token
     */
    public String generateToken(UserDetails userDetails) {
        Map<String, Object> claims = new HashMap<>(2);
        claims.put("sub", userDetails.getUsername());
        claims.put("created", new Date());
        return generateToken(claims);
    }

    /**
     * Get user name from token
     *
     * @param token token
     * @return user name
     */
    public String getUsernameFromToken(String token) {
        String username;
        try {
            Claims claims = getClaimsFromToken(token);
            if(null == claims){
            	return null;
            }
            username = claims.getSubject();
        } catch (Exception e) {
            username = null;
        }
        return username;
    }

    /**
     * Determine whether the token has expired
     *
     * @param token token
     * @return Expired
     */
    public Boolean isTokenExpired(String token) {
        try {
            Claims claims = getClaimsFromToken(token);
            if(null == claims){
            	return false;
            }
            Date expiration = claims.getExpiration();
            return expiration.before(new Date());
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * refresh token 
     *
     * @param token Original token
     * @return New token
     */
    public String refreshToken(String token) {
        String refreshedToken;
        try {
            Claims claims = getClaimsFromToken(token);
            if(null == claims){
            	return null;
            }
            claims.put("created", new Date());
            refreshedToken = generateToken(claims);
        } catch (Exception e) {
            refreshedToken = null;
        }
        return refreshedToken;
    }

    /**
     * Authentication token
     *
     * @param token       token
     * @param userDetails user
     * @return Is it valid
     */
    public Boolean validateToken(String token, UserDetails userDetails) {
        JwtUser user = (JwtUser) userDetails;
        String username = getUsernameFromToken(token);
        return (username.equals(user.getUsername()) && !isTokenExpired(token));
    }
    @RequestMapping(value="login",method = RequestMethod.POST)
    public ReturnVo login(String username, String password,HttpServletResponse
            response) {
        User user =  userService.findByUsername(username);
        if(user!=null){
            if(user.getPassword().equals(password)){
                //Return the token to the client -- > the client saves it to a cookie -- > the client attaches a cookie parameter to each request
                String JWT = JwtUtils.createJWT("1", username, SystemConstant.JWT_TTL);
                return ReturnVo.ok(JWT);
            }else{
                return ReturnVo.error();
            }
        }else{
            return ReturnVo.error();
        }
    }


    @RequestMapping(value="description",method = RequestMethod.POST)
    public ReturnVo description(String username) {
        User user =  userRepository.findByUsername(username);
        return ReturnVo.ok(user.getDescription());
    }

There is another way to generate jwt:

maven dependency

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

JwtUtils tool class

public class JavaJWTUtil {
    // Expiration time 5 minutes
    private static final long EXPIRE_TIME = 5 * 60 * 1000;

    /**
     * Generate signature and expire in 5min
     *
     * @param username user name
     * @param secret   User's password
     * @return Encrypted token
     */
    public static String sign(String username, String secret) {
        Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME);
        Algorithm algorithm = Algorithm.HMAC256(secret);
        // With username information
        return JWT.create()
                .withClaim("username", username)
                .withClaim("as", "a")
                .withExpiresAt(date)
                .sign(algorithm);
    }

    public static boolean verify(String token, String username, String secret) {
        try {
            Algorithm algorithm = Algorithm.HMAC256(secret);
            JWTVerifier verifier = JWT.require(algorithm)
                    .withClaim("username", username)
                    .build();
            DecodedJWT jwt = verifier.verify(token);
            System.out.println(token);
            System.out.println(jwt.getHeader());
            System.out.println(jwt.getPayload());
            System.out.println(jwt.getSignature());
            System.out.println(jwt.getToken());
            return true;
        } catch (Exception exception) {
            exception.printStackTrace();
            return false;
        }
    }
    public static void main(String[] args) {
        String zhangsan = JavaJWTUtil.sign("zhangsan", "123");
        JavaJWTUtil.verify(zhangsan, "zhangsan", "123");
    }
}

Topics: Java Web Development Spring Back-end