SpringBoot integrates JWT to implement token verification and code demonstration

Posted by mohammedsk on Tue, 08 Feb 2022 15:24:50 +0100

1, Main application scenarios of JWT

Identity authentication in this scenario, once the user completes the login, JWT is included in each subsequent request, which can be used to verify the user's identity and the access rights to routes, services and resources. Because its cost is very small, it can be easily transmitted in systems with different domain names. At present, this technology is widely used in single sign on (SSO). Information exchange using JWT to encode data between the two sides of communication is a very secure way. Because its information is signed, it can ensure that the information sent by the sender is not forged.

advantage

1. Compact: it can be sent through URL, POST parameter or HTTP header, because the amount of data is small and the transmission speed is fast
2. Self contained: the load contains the information required by all users, avoiding multiple queries to the database
3. Because tokens are stored on the client in the form of JSON encryption, JWT is cross language and is supported in any web form in principle.
4. There is no need to save session information on the server, which is especially suitable for distributed microservices.

`

2, Structure of JWT

JWT is composed of three pieces of information, which are used as text Join together to form a JWT string.
Like this:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

JWT consists of three parts:
Header header (the header contains the metadata of the token and contains the type of signature and / or encryption algorithm)
Payload load (similar to items carried on an aircraft)
Signature signature / visa

Header

The header of JWT carries two parts of information: token type and encryption algorithm.

 

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

Declaration type: jwt here
Algorithm for declaring encryption: HMAC SHA256 is usually used directly

Encryption algorithm is a one-way function hash algorithm. Common algorithms include MD5, SHA and HAMC.
MD5 (message digest algorithm 5) abbreviation, widely used in encryption and decryption technology, commonly used in file verification. Check? No matter how large the file is, a unique MD5 value can be generated after MD5
SHA (Secure Hash Algorithm), an important tool in cryptography applications such as digital signature, has higher security than MD5
HMAC (Hash Message Authentication Code), Hash message authentication code, authentication protocol of Hash algorithm based on key. The public function and key are used to generate a fixed length value as the authentication identifier, which is used to identify the integrity of the message. Commonly used for interface signature verification

Payload

Payload is the place where valid information is stored.
Valid information consists of three parts
1. Declaration registered in the standard
2. Public statements
3. Declaration of private ownership

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

iss: jwt issuer
sub: user oriented (jwt user oriented)
aud: party receiving jwt
exp: expiration time stamp (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.

Public statement:

Any information can be added to the public statement. Generally, the user's relevant information or other necessary information required by the business can be added However, it is not recommended to add sensitive information because this part can be decrypted on the client

Private declaration:

Private declaration is a declaration jointly defined by providers and consumers. It is generally not recommended to store sensitive information, because base64 is symmetrically decrypted, which means that this part of information can be classified as plaintext information.

Signature

The third part of jwt is a visa information
This part needs base64 encrypted header and base64 encrypted payload The string formed by connection is then encrypted by salt secret combination through the encryption method declared in the header, and then constitutes the third part of jwt.
The secret key is stored in the server. The server will generate and verify the token based on this key, so it needs to be protected.

3, Let's integrate SpringBoot and JWT

(1)application.yml

server:
  port: 7009
spring:
  application:
    name: ware-jwt-token
config:
  jwt:
    # encryption key 
    secret: iwqjhda8232bjgh432[cicada-smile]
    # Valid duration of token
    expire: 3600
    # header name
    header: token

(2) General tool class JwtConfig


@ConfigurationProperties(prefix = "config.jwt")
@Component
public class JwtConfig {
    /*
     * Generate a Token according to the identity ID
     */
    public String getToken (String identityId){
        Date nowDate = new Date();
        //Expiration time
        Date expireDate = new Date(nowDate.getTime() + expire * 1000);
        return Jwts.builder()
                .setHeaderParam("typ", "JWT")
                .setSubject(identityId)
                .setIssuedAt(nowDate)
                .setExpiration(expireDate)
                .signWith(SignatureAlgorithm.HS512, secret)
                .compact();
    }
    /*
     * Get the registration information in the Token
     */
    public Claims getTokenClaim (String token) {
        try {
            return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
        }catch (Exception e){
            e.printStackTrace();
            return null;
        }
    }
    /*
     * Token Expire validation
     */
    public boolean isTokenExpired (Date expirationTime) {
        return expirationTime.before(new Date());
    }
    private String secret;
    private long expire;
    private String header;
    public String getSecret() {
        return secret;
    }
    public void setSecret(String secret) {
        this.secret = secret;
    }
    public long getExpire() {
        return expire;
    }
    public void setExpire(long expire) {
        this.expire = expire;
    }
    public String getHeader() {
        return header;
    }
    public void setHeader(String header) {
        this.header = header;
    }
}

(3) General tool class: WebConfig interceptor registration

@Configuration
public class WebConfig implements WebMvcConfigurer {
    /**
     * Interceptor registration
     */
    @Resource
    private TokenInterceptor tokenInterceptor ;
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(tokenInterceptor).addPathPatterns("/**");
    }
}

/**
 * Token Interceptor
 */
@Component
public class TokenInterceptor extends HandlerInterceptorAdapter {
    @Resource
    private JwtConfig jwtConfig ;
    @Override
    public boolean preHandle(HttpServletRequest request,
                             HttpServletResponse response,
                             Object handler) throws Exception {
        // Address filtering
        String uri = request.getRequestURI() ;
        if (uri.contains("/login")){
            return true ;
        }
        // Token verification
        String token = request.getHeader(jwtConfig.getHeader());
        if(StringUtils.isEmpty(token)){
            token = request.getParameter(jwtConfig.getHeader());
        }
        if(StringUtils.isEmpty(token)){
            throw new Exception(jwtConfig.getHeader()+ "Cannot be empty");
        }
        Claims claims = jwtConfig.getTokenClaim(token);
        if(claims == null || jwtConfig.isTokenExpired(claims.getExpiration())){
            throw new Exception(jwtConfig.getHeader() + "Invalid, please login again");
        }
        //Set identityId user ID
        request.setAttribute("identityId", claims.getSubject());
        return true;
    }
}

(4) Write a Controller to test


@RestController
public class TokenController {
    @Resource
    private JwtConfig jwtConfig ;
    /*
     * Back parameter format
     * {
     *     "userName": "ID123",
     *     "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.
     *               eyJzdWIiOiJJRDEyM3B3MTIzIiwiaWF0Ijox.
     *               SqqaZfG_g2OMijyN5eG0bPmkIQaqMRFlUvny"
     * }
     */
    // The interceptor releases directly and returns a Token
    @PostMapping("/login")
    public Map<String,String> login (@RequestParam("userName") String userName,
                                     @RequestParam("passWord") String passWord){
        Map<String,String> result = new HashMap<>() ;
        // Omit data source verification
        String token = jwtConfig.getToken(userName+passWord) ;
        if (!StringUtils.isEmpty(token)) {
            result.put("token",token) ;
        }
        result.put("userName",userName) ;
        return result ;
    }
    // Interface requiring Token verification
    @PostMapping("/info")
    public String info (){
        return "info" ;
    }
}

(5) The running result registers a token

(5) The running result does not carry the request header

(5) The running result carries the request header

Topics: Java jwt