JWT

What is a JSON Web Token?

JSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact and independent way to securely transfer information between parties as JSON objects. This information can be authenticated and trusted with a digital signature. JWT can sign using secret (using the HMAC algorithm) or using RSA or ECDSA’s public/private key pair.

While JWT can be encrypted to provide confidentiality between parties, we will focus on signing tokens. A signed token verifies the integrity of the declarations contained therein, while an encrypted token hides the declarations of other parties. When a public/private key pair is used to sign a token, the signature also proves that only the party holding the private key is the party signing it.

When should YOU use a JSON Web token?

Here are some scenarios where JSON Web tokens are useful:

  • Authorization: This is the most common scenario for using JWT. Once the user is logged in, each subsequent request will include a JWT that allows the user to access the routes, services, and resources that the token allows. Single Sign On is a feature that is now widely used in JWT because of its low overhead and ease of use in different domains.
  • Information exchange: JSON Web tokens are a great way to securely transfer information between parties. Because JWT can sign – using a public/private key pair, for example – you can be sure the sender is who they say it is. In addition, because the header and payload are used to compute the signature, you can verify that the content has not been tampered with.

What is a JSON Web token structure?

In compact form, JSON Web Tokens are made of dot (.) The three parts of the partition are:

  • Header
  • Payload
  • Signature

Therefore, JWT is usually as follows.

xxxxx.yyyyy.zzzzz
Copy the code

Let’s break down the different parts.

Header

The header usually consists of two parts: the type of token, which is JWT, and the signature algorithm being used, such as HMAC SHA256 or RSA.

Such as:

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

This JSON is then encoded as Base64Url to form the first part of the JWT.

Payload

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

  • Registered declarations: These are a set of predefined declarations that are not mandatory, but are recommended to provide a useful, interoperable set of declarations. Some of them are iss (Issuer), EXP (expiration time), Sub (subject), AUD (audience), etc.

    Note that the declaration name is only three characters long, because JWT means compact.

  • Public disclaimer: These can be defined at will by those who use JWT. But to avoid collisions, they should be defined in the IANA JSON Web token registry, or as urIs that contain a conflict-proof namespace.

  • Private declarations: Private declarations are defined by both the provider and the consumer. It is generally not recommended to store sensitive information because Base64 is symmetrically decrypted, meaning that part of the information can be classified as plaintext information.

Example payload can be:

{
  "sub": "1234567890"."name": "John Doe"."admin": true
}
Copy the code

The payload is then base64URL-encoded to form the second part of the JSON Web token.

Note that for signed tokens, this information, while tamper-proof, can be read by anyone. Do not put secret messages in the payload or header of the JWT unless encrypted.

Signature

To create a signature part, you must take the algorithms specified in header, payload, secret, and header and sign them.

For example, if you want to use the HMAC SHA256 algorithm, you create a signature as follows:

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret)
Copy the code

The signature is used to verify that the message has not been changed during this process, and, in the case of a token signed with a private key, it can also verify that the JWT sender is who it claims to be.

Put it all together

The output is three dot-separated Base64-URL strings that can be passed easily in HTML and HTTP environments and are more compact than XML-based standards such as SAML.

A JWT with the previous header and payload encoding is shown below, and uses a confidential signature.

How JWT works

1) The user sends a POST request using the user name and password when logging in.

2) The server uses the private key to create a JWT.

3) The server returns the JWT to the browser.

4) The browser adds the JWT string to the request header and sends the request to the server.

5) Server validation JWT.

6) Return the corresponding resource to the client.

SpringBoot integration

Introduction of depend on

<dependency> <groupId>com.auth0</groupId> <artifactId> Java -jwt</artifactId> <version>3.4.0</version> </dependency>Copy the code

Custom annotation

PassToken: Skip validation.

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface PassToken {
    boolean required() default true;
}
Copy the code

UserLoginToken: Requires authentication.

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface UserLoginToken {
    boolean required() default true;
}
Copy the code

Defining entity Classes

public class UserDo {
    private String id;

    private String userName;

    private String userPasswd;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id == null ? null : id.trim();
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName == null ? null : userName.trim();
    }

    public String getUserPasswd() {
        return userPasswd;
    }

    public void setUserPasswd(String userPasswd) { this.userPasswd = userPasswd == null ? null : userPasswd.trim(); }}Copy the code

Write TokenServiceImpl

@Service
public class TokenServiceImpl implements TokenService {
    @Override
    public String getToken(UserDo userDo) {
        String token="";
        token= JWT.create().withAudience(userDo.getId())
                .sign(Algorithm.HMAC256(userDo.getUserPasswd()));
        returntoken; }}Copy the code

Writing interceptors

public class AuthenticationInterceptor implements HandlerInterceptor {
    @Reference
    UserService userService;

    @Override
    public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object object) throws Exception {
        String token = httpServletRequest.getHeader("token"); // Fetch the token from the HTTP request header // If not mapped to the method directly throughif(! (object instanceof HandlerMethod)) {return true; } HandlerMethod handlerMethod = (HandlerMethod) object; Method method = handlerMethod.getMethod(); // Check if there are passtoken annotations, skip authentication if there areif (method.isAnnotationPresent(PassToken.class)) {
            PassToken passToken = method.getAnnotation(PassToken.class);
            if (passToken.required()) {
                return true; }} // Check for annotations that require user permissionsif (method.isAnnotationPresent(UserLoginToken.class)) {
            UserLoginToken userLoginToken = method.getAnnotation(UserLoginToken.class);
            if(userLogintoken.required ()) {// Perform authenticationif (token == null) {
                    throw new RuntimeException("No token, please log in again"); } // Get the user ID in the token String userId; try { userId = JWT.decode(token).getAudience().get(0); } catch (JWTDecodeException j) { throw new RuntimeException("401");
                }
                UserDo user = userService.findUserById(userId);
                if (user == null) {
                    throw new RuntimeException("User does not exist, please log in again."); JWTVerifier = jwt.require (algorithm.hmac256 (user.getUserpasswd ())).build(); try { jwtVerifier.verify(token); } catch (JWTVerificationException e) { throw new RuntimeException("401");
                }
                return true; }}return true;
    }

    @Override
    public void postHandle(HttpServletRequest httpServletRequest,
                           HttpServletResponse httpServletResponse,
                           Object o, ModelAndView modelAndView) throws Exception {

    }

    @Override
    public void afterCompletion(HttpServletRequest httpServletRequest,
                                HttpServletResponse httpServletResponse,
                                Object o, Exception e) throws Exception {
    }
}

Copy the code

The flow chart is as follows:

Configuring interceptors

@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(authenticationInterceptor())
                .addPathPatterns("/ * *");
    }
    @Bean
    public AuthenticationInterceptor authenticationInterceptor() {
        returnnew AuthenticationInterceptor(); }}Copy the code

controller-api

@RestController
@CrossOrigin
@RequestMapping("/user")
@Api(tags = "User Login") public class LoginController extends BaseController { @Reference UserService userService; @Reference TokenService tokenService; / / login @ PostMapping ("/login")
    @ResponseBody
    public Object login(UserDo user) {
        JSONObject jsonObject = new JSONObject();
        UserDo userForBase = userService.findUserByUsername(user.getUserName());
        if (userForBase == null) {
            jsonObject.put("message"."Login failed, user does not exist");
            return jsonObject;
        } else {
            if(! userForBase.getUserPasswd().equals(user.getUserPasswd())) { jsonObject.put("message"."Login failed with incorrect password");
                return jsonObject;
            } else {
                String token = tokenService.getToken(userForBase);
                jsonObject.put("token", token);
                jsonObject.put("user", userForBase);
                return jsonObject;
            }
        }
    }

    @UserLoginToken
    @GetMapping("/getMessage")
    public String getMessage() {
        return "You've been verified."; }}Copy the code

The getMessage () method can only be called if the token is passed in.

Interface debugging

Call to getMessage ()

Use the postmen call interface: http://localhost:8094/user/getMessage

The following error information is displayed:

Call the login ()

Use the postmen call interface: http://localhost:8094/user/login

Pass in id, username, and password, and the output is as follows:

Call getMessage() again

Add the token parameter to Headers. The following output is displayed: