Spring boot integrates JWT to implement token verification process

Time:2020-5-18

JWT official website: https://jwt.io/

GitHub address of JWT (Java version): https://github.com/jwtk/jjjwt

What is JWT

JSON web token (JWT) is a kind of network application based onJSONThe open standard (RFC 7519) defines a concise, self-contained method for communication between two partiesJSONThe formal security of objects. Because of the existence of digital signatures, this information is trusted and JWT can useHMACAlgorithm orRSAThe public and private key pair of.

JWT request process

1. The user uses the account and face to send the post request; 2. The server uses the private key to create a JWT; 3. The server returns the JWT to the browser; 4. The browser sends the JWT string in the request header like the server; 5. The server verifies the JWT; 6. The resource returned to the browser.

Main application scenarios of JWT

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 of routes, services and resources. Because its cost is very small, it can be easily transferred in different domain name systems, and it is widely used in single sign on (SSO) at present. Information exchange is a very safe way to use JWT to encode data between the two sides of communication. Because its information is signed, it can ensure that the information sent by the sender is not forged.

advantage

1. Compact: you can useURLPOSTParameter or inHTTP headerSelf contained: the load contains all the information needed by users, avoiding multiple queries to the database. 3TokenSoJSONThe encrypted form is stored in the client’s, soJWTIt is cross language, and in principle, it is supported in any form of web. 4. It does not need to save session information on the server, especially for distributed microservices.

`

Structure of JWT

JWT is composed of three pieces of information, which are.The join together forms the JWT string. Like this:


eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

JWT consists of three parts: header header (the header contains the metadata of the token and the type of signature and / or encryption algorithm); payload payload (similar to the items carried on the aircraft); signature signature / visa

Header

JWT header 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, the common ones are 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, after MD5, it can generate unique MDA (secure hash algorithm), digital signature and other important tools in cryptography applications. The security is higher than that of MD5 HMAC (hash message authentication code), hash message authentication code and authentication protocol of hash algorithm based on key. The public function and the key are used to generate a fixed length value as the authentication identifier, which is used to authenticate the integrity of the message. Commonly used for interface signature verification

Payload

The load is where the payload is stored.

Valid information consists of three parts: 1. Declaration registered in the standard 2. Public declaration 3. Private declaration

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

iss: JWT issuersub: users targeted (users targeted by JWT)aud: the party receiving JWTexp: expiration timestamp (the expiration time of JWT, which must be greater than the issuing time)nbf: defines when the JWT will be unavailableiat: when JWT was issuedjti: the unique identity of JWT, mainly used as a one-timetokenTo avoid replay attacks.

Public statement:

Public declaration can add any information, generally related information of users or other necessary information required by business. However, it is not recommended to add sensitive information, because this part can be decrypted in the client

Private statement:

A private statement is a statement defined by both the provider and the consumer. It is generally not recommended to store sensitive information becausebase64It is symmetric decryption, which means that this part of information can be classified as plaintext information.

Signature

The third part of JWT is a visa information, which needsbase64Encryptedheaderandbase64Encryptedpayloaduse.Connect the string, and then use theheaderAdding salt in the encryption method stated insecretCombined encryption, thenjwtThe third part of. secret keysecretIt is saved in the server. The server will generate it according to this keytokenAnd, so it needs to be protected.

Let’s integrate springboot and JWT

introduceJWTDependency, because it is based onJava, so what we need isjava-jwt


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

Two comments need to be customized

Used to skip validationPassToken


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

Annotation requiring login for operationUserLoginToken


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

@Target: purpose of annotation

@Target(ElementType.TYPE)——Interface, class, enumeration, annotation@Target(ElementType.FIELD)——Constants for fields, enumerations@Target(ElementType.METHOD)——Methods@Target(ElementType.PARAMETER)——Method parameters@Target(ElementType.CONSTRUCTOR)——Constructor@Target(ElementType.LOCAL_VARIABLE)——Local variable@Target(ElementType.ANNOTATION_TYPE)——Notes@Target(ElementType.PACKAGE)——Bag

@Retention: reserved position of annotation

RetentionPolicy.SOURCE: this type ofAnnotationsReserved only at the source level and ignored at compile timeclassNot in bytecode file.RetentionPolicy.CLASS: this type ofAnnotationsReserved at compile time. The default retention policy is set atclassFile, butJVMWill be ignored, not available at runtime.RetentionPolicy.RUNTIME: this type ofAnnotationsWill beJVMKeep, so they can beJVMOr other code that uses the reflection mechanism.@Document: indicates that the annotation will be included in thejavadocin@Inherited: indicates that the child class can inherit the annotation in the parent class

Simple customization of an entity classUser, usinglombokSimplify the writing of entity classes


@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
 String Id;
 String username;
 String password;
}

Need to writetokenGeneration method of


public String getToken(User user) {
 String token="";
 token= JWT.create().withAudience(user.getId())
  .sign(Algorithm.HMAC256(user.getPassword()));
 return token;
 }

Algorithm.HMAC256(): UsingHS256generatetoken, the key is the user’s password, and the only key can be saved in the server.withAudience()Deposit needs to be saved intokenHere I put the userIDDeposittokenin

Next we need to write an interceptor to gettokenAnd verifytoken

public class AuthenticationInterceptor implements HandlerInterceptor {
 @Autowired
 UserService userService;
 @Override
 public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object object) throws Exception {
 String token = HttpServletRequest. Getheader ("token"); // take the token from the HTTP request header
 //If it is not mapped to a method directly through
 if(!(object instanceof HandlerMethod)){
  return true;
 }
 HandlerMethod handlerMethod=(HandlerMethod)object;
 Method method=handlerMethod.getMethod();
 //Check whether there is a passtoken comment, and skip authentication if there is one
 if (method.isAnnotationPresent(PassToken.class)) {
  PassToken passToken = method.getAnnotation(PassToken.class);
  if (passToken.required()) {
  return true;
  }
 }
 //Check for comments that require user rights
 if (method.isAnnotationPresent(UserLoginToken.class)) {
  UserLoginToken userLoginToken = method.getAnnotation(UserLoginToken.class);
  if (userLoginToken.required()) {
  //Perform certification
  if (token == null) {
   Throw new runtimeException ("no token, please login again");
  }
  //Get user ID in token
  String userId;
  try {
   userId = JWT.decode(token).getAudience().get(0);
  } catch (JWTDecodeException j) {
   throw new RuntimeException("401");
  }
  User user = userService.findUserById(userId);
  if (user == null) {
   Throw new runtimeException ("user does not exist, please login again");
  }
  //Verify token
  JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(user.getPassword())).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 {
 }

To implement an interceptor, you need to implementHandlerInterceptorInterface

HandlerInterceptorInterface mainly defines three methods 1boolean preHandle (): preprocessing callback method to realize preprocessing of the processor. The third parameter is the response processor, customizedController, return value istrueRepresents the continuation of a process, such as calling the next interceptor or processor, or the subsequent executionpostHandle()andafterCompletion()falseIndicates that the process is interrupted, and other interceptors or processors will not be called continuously to interrupt execution.

2.void postHandle(): callback method of post-processing to realize post-processing of processor(DispatcherServletMake a view return call before rendering), at this time, we can use themodelAndView(model and view objects) to process model data or views,modelAndViewOr maybenull

3.void afterCompletion(): callback method after processing the whole request, which also needs the current correspondingInterceptorOfpreHandle()Only when the return value of is true, that is to sayDispatcherServletExecuted after rendering the corresponding view. For resource cleanup. Callback method after processing the whole request. For example, in performance monitoring, we can record the end time and output the consumption time here, and we can also clean up some resources, similar totry-catch-finallyInfinally, but only in the processor execution chain

Main process:

1. FromhttpRemove from request headertoken, 2. Judge whether to map to method 3. Check whether there ispasstokenNote: If yes, skip the authentication. 4. Check if there are any notes that require the user to log in. If yes, take them out and verify them. 5. If the authentication is passed, you can access them. If it is not passed, an error message will be reported

Configuring Interceptors

Added annotation on configuration class@Configuration, indicating that the class is a configuration class and will be treated as aSpringBeanAdd toIOCIn container

@Configuration
public class InterceptorConfig extends WebMvcConfigurerAdapter {
 @Override
 public void addInterceptors(InterceptorRegistry registry) {
 registry.addInterceptor(authenticationInterceptor())
  . addpathpatterns ("/ * *"); // block all requests, and determine whether login is required by judging whether there is @ loginrequired annotation
 }
 @Bean
 public AuthenticationInterceptor authenticationInterceptor() {
 return new AuthenticationInterceptor();
 }
}

WebMvcConfigurerAdapterIn fact, there is no method implementation in the abstract class, but the interface is emptyWebMvcConfigurerAll the methods in do not provide any business logic processing. This design is just right so that we don’t have to implement the methods we don’t use. It’s up toWebMvcConfigurerAdapterAbstract class empty implementation, if we need to make logical processing for a specific method, we only need to

WebMvcConfigurerAdapterIn subclass@OverrideThe corresponding method is OK.

Note:

staySpringBoot2.0andSpring 5.0inWebMvcConfigurerAdapterIt has been abandoned. It has been changed to inheritanceWebMvcConfigurationSupport, but after a try, it’s still overdue

resolvent:

Direct implementationWebMvcConfigurer(official recommendation)


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

InterceptorRegistryInternaladdInterceptorNeed an implementationHandlerInterceptorInterceptor instance of interface,addPathPatternsMethod is used to set filter path rules for interceptors. Here I intercept all requests, and judge whether there are any@LoginRequiredComments determine whether login is required

Add login operation annotation to data provider

@RestController
@RequestMapping("api")
public class UserApi {
 @Autowired
 UserService userService;
 @Autowired
 TokenService tokenService;
 //Log in
 @PostMapping("/login")
 public Object login(@RequestBody User user){
 JSONObject jsonObject=new JSONObject();
 User userForBase=userService.findByUsername(user);
 if(userForBase==null){
  Jsonobject. Put ("message", "login failed, user does not exist");
  return jsonObject;
 }else {
  if (!userForBase.getPassword().equals(user.getPassword())){
  jsonObject.put ("message", "login failed, password error");
  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 have passed the verification";
 }
}

If there is no annotation, it is not verified by default, and the login interface is generally not verified. staygetMessage()I added the login annotation to indicate that the interface must be logged in to obtaintokenAfter that, add thetokenAnd can only be accessed through verification

Now test, start the project, and use postman to test the interface

NotokenAccess in case ofapi/getMessageInterface

             

I use unified exception handling here, so I only see errorsmessage

Log in to gettoken

I didn’t annotate the login operation, so I can access it directly

holdtokenAdd to request header, visit againapi/getMessageInterface

Note: herekeyIt must not be wrong, because the key words are taken in the interceptortokenValue ofString token = httpServletRequest.getHeader("token");

addtokenAfter that, you can successfully pass the verification and access the interface

Source code address of GitHub project: https://github.com/jinbinpeng/springboot-jwt

summary

The above is the spring boot integrated JWT implementation token verification process introduced by Xiaobian to you. I hope it can help you. If you have any questions, please leave me a message and Xiaobian will reply to you in time. Thank you very much for your support of the developepaer website! If you think this article is helpful to you, welcome to reprint, please indicate the source, thank you!