Spring boot integrates JWT to implement token verification

Original text: https://www.jianshu.com/p/e88d3f8151db

JWT official website: https://jwt.io/
github address of JWT(Java version): https://github.com/jwtk/jjwt

What is JWT?

Json web token (JWT) is a JSON based open standard (RFC 7519) which is implemented to deliver statements between network application environments. It defines a concise, self-contained method for the secure delivery of information in the form of JSON objects between communication parties. Because of the existence of digital signature, the information is trusted. JWT can use HMAC algorithm or RSA private private key pair to sign.

JWT request process

 

 

1. The user uses the account and face to send a 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 strings the jwt in the request header to send the request to the server;
5. The server verifies the jwt;
6. Return the response resources 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: it can be sent through URL, POST parameter or HTTP header, because the data volume 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 Token is stored in the client in the form of JSON encryption, JWT is cross language, and in principle, any web form supports it.
4. It does not need to save session information on the server, especially for distributed microservices.

`

The structure of JWT

JWT is composed of three pieces of information, which are joined together by. To form a JWT string.
Like this:

1 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 load (similar to the load on the aircraft)
Signature / visa

Header

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

1 { 
2   "alg": "HS256",
3    "typ": "JWT"
4 } 

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, the unique MD5 value can be generated after MD5
SHA (Secure Hash Algorithm), digital signature and other important tools in cryptography applications, with 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 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 statement
3. Private declaration

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

iss: jwt issuer
sub: user for (jwt user)
aud: the party receiving jwt
exp: expiration timestamp (the expiration time of jwt, which must be greater than the issuing time)
nbf: defines when the jwt will not be available
IAT: issuing time of JWT
JTI: the unique identity of JWT, which is mainly used as a one-time token to avoid replay attack.

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:

Private statement is a statement defined by both the provider and the consumer. It is generally not recommended to store sensitive information, because base64 is symmetric decrypted, which means that this part of information can be classified as clear text information.

Signature

The third part of jwt is a visa information
This part requires that base64 encrypted header and base64 encrypted payload use the string composed of. Connection, and then use the declared encryption method in header to add salt secret combination encryption, and then constitute the third part of jwt.
The secret key is saved in the server. The server will generate a token and verify it according to the key, so it needs to be protected.

Let's integrate spring boot and JWT

The introduction of JWT dependency requires Java JWT because it is based on Java

1 <dependency>
2       <groupId>com.auth0</groupId>
3       <artifactId>java-jwt</artifactId>
4       <version>3.4.0</version>
5 </dependency>

Two comments need to be customized

PassToken used to skip authentication

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

Annotation UserLoginToken requiring login for operation

1 @Target({ElementType.METHOD, ElementType.TYPE})
2 @Retention(RetentionPolicy.RUNTIME)
3 public @interface UserLoginToken {
4     boolean required() default true;
5 }
@Target: the target of annotation

@Target(ElementType.TYPE) - interface, class, enumeration, annotation
@Target(ElementType.FIELD) - constant of field and enumeration
@Target(ElementType.METHOD) - Method
@Target(ElementType.PARAMETER) - method parameter
@Target(ElementType.CONSTRUCTOR) - constructor
@Target (ElementType. Local "variable) -- local variable
@Target (ElementType. Annotation? Type) - Annotation
@Target(ElementType.PACKAGE) - package

@Retention: where annotations are kept

RetentionPolicy.SOURCE: Annotations of this type are reserved only at the source level and will be ignored at compile time, not included in the class bytecode file.
RetentionPolicy.CLASS: this type of Annotations is reserved at compile time. The default retention policy exists in the class file, but the JVM will ignore it and cannot obtain it at run time.
RetentionPolicy.RUNTIME: Annotations of this type will be reserved by the JVM, so they can be read and used by the JVM or other code using reflection mechanism at runtime.
@Document: indicates that the annotation will be included in javadoc
@Inherited: indicates that the child class can inherit the annotation in the parent class

Simply customize an entity class User, and use lombok to simplify the preparation of entity classes

1 @Data
2 @AllArgsConstructor
3 @NoArgsConstructor
4 public class User {
5     String Id;
6     String username;
7     String password;
8 }

Need to write token generation method

1 public String getToken(User user) {
2         String token="";
3         token= JWT.create().withAudience(user.getId())
4                 .sign(Algorithm.HMAC256(user.getPassword()));
5         return token;
6     }

Algorithm.HMAC256(): HS256 is used to generate a token. The key is the user's password. The only key can be saved in the server.
With audience () stores the information that needs to be saved in the token. Here I save the user ID into the token

Next, you need to write an interceptor to get the token and verify it

 1 public class AuthenticationInterceptor implements HandlerInterceptor {
 2     @Autowired
 3     UserService userService;
 4     @Override
 5     public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object object) throws Exception {
 6         String token = httpServletRequest.getHeader("token");// from http Remove from request header token
 7         // If it is not mapped to a method directly through
 8         if(!(object instanceof HandlerMethod)){
 9             return true;
10         }
11         HandlerMethod handlerMethod=(HandlerMethod)object;
12         Method method=handlerMethod.getMethod();
13         //Check if there is passtoken Note, skip authentication if any
14         if (method.isAnnotationPresent(PassToken.class)) {
15             PassToken passToken = method.getAnnotation(PassToken.class);
16             if (passToken.required()) {
17                 return true;
18             }
19         }
20         //Check for comments that require user rights
21         if (method.isAnnotationPresent(UserLoginToken.class)) {
22             UserLoginToken userLoginToken = method.getAnnotation(UserLoginToken.class);
23             if (userLoginToken.required()) {
24                 // Executive certification
25                 if (token == null) {
26                     throw new RuntimeException("nothing token,Please login again");
27                 }
28                 // Obtain token Medium user id
29                 String userId;
30                 try {
31                     userId = JWT.decode(token).getAudience().get(0);
32                 } catch (JWTDecodeException j) {
33                     throw new RuntimeException("401");
34                 }
35                 User user = userService.findUserById(userId);
36                 if (user == null) {
37                     throw new RuntimeException("User does not exist, please login again");
38                 }
39                 // Verification token
40                 JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(user.getPassword())).build();
41                 try {
42                     jwtVerifier.verify(token);
43                 } catch (JWTVerificationException e) {
44                     throw new RuntimeException("401");
45                 }
46                 return true;
47             }
48         }
49         return true;
50     }
51 
52     @Override
53     public void postHandle(HttpServletRequest httpServletRequest, 
54                                   HttpServletResponse httpServletResponse, 
55                             Object o, ModelAndView modelAndView) throws Exception {
56 
57     }
58     @Override
59     public void afterCompletion(HttpServletRequest httpServletRequest, 
60                                           HttpServletResponse httpServletResponse, 
61                                           Object o, Exception e) throws Exception {
62     }

To implement an interceptor, you need to implement the HandlerInterceptor interface

The HandlerInterceptor interface mainly defines three methods
1.boolean preHandle ():
Preprocessing callback method to implement preprocessing of the processor. The third parameter is the response processor, user-defined Controller. A return value of true means to continue the process (such as calling the next interceptor or processor) or to execute next
postHandle() and afterCompletion(); false indicates that the process is interrupted, and other interceptors or processors will not be called again to interrupt execution.

2.void postHandle():
The post-processing callback method implements the post-processing of the processor (the dispatcher servlet calls before the view returns to the rendering). At this time, we can process the model data or the view through the modelAndView (model and view object), and the modelAndView can also be null.

3.void afterCompletion():
After the whole request is processed, the callback method is executed only when the return value of the current corresponding Interceptor's preHandle() is true, that is, after the dispatcher servlet renders 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 also clean up some resources, similar to finally in try catch finally, but only call the processor execution chain

Main process:

1. Take the token from the http request header,
2. Judge whether to map to method
3. Check whether there is a passtoken comment, and skip the authentication if there is one
4. Check if there are any comments that need to be logged in by the user. If there are any, they need to be taken out and verified
5. If the authentication is passed, it can be accessed. If it is not passed, relevant error information will be reported

Configuring Interceptors

The @ Configuration annotation is added to the Configuration class, indicating that the class is a Configuration class and will be added to the IOC container as a spring bean

 1 @Configuration
 2 public class InterceptorConfig extends WebMvcConfigurerAdapter {
 3     @Override
 4     public void addInterceptors(InterceptorRegistry registry) {
 5         registry.addInterceptor(authenticationInterceptor())
 6                 .addPathPatterns("/**");    // Block all requests by judging whether there are @LoginRequired Comments determine whether login is required
 7     }
 8     @Bean
 9     public AuthenticationInterceptor authenticationInterceptor() {
10         return new AuthenticationInterceptor();
11     }
12 }

WebMvcConfigurerAdapter there is no method implementation in this abstract class, but the interface is empty
All the methods in WebMvcConfigurer 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. All of them are implemented by WebMvcConfigurerAdapter abstract class. If we need to make logic processing for a specific method, we just need to
The @ Override corresponding method in the WebMvcConfigurerAdapter subclass is OK.

Note:
WebMvcConfigurerAdapter has been discarded in SpringBoot2.0 and Spring 5.0
It's said on the Internet that WebMvcConfigurationSupport should be inherited, but it's expired after trying

resolvent:

Directly implement WebMvcConfigurer (official recommendation)

 1 @Configuration
 2 public class InterceptorConfig implements WebMvcConfigurer {
 3     @Override
 4     public void addInterceptors(InterceptorRegistry registry) {
 5         registry.addInterceptor(authenticationInterceptor())
 6                 .addPathPatterns("/**");   
 7     }
 8     @Bean
 9     public AuthenticationInterceptor authenticationInterceptor() {
10         return new AuthenticationInterceptor();
11     }
12 }

The addInterceptor in the interceptor registry needs an interceptor instance that implements the HandlerInterceptor interface. The addPathPatterns method is used to set the filtering path rules of the interceptor.
Here, I block all requests and decide whether to log in by judging whether there is @ LoginRequired annotation

Add login operation annotation to data provider

 1 @RestController
 2 @RequestMapping("api")
 3 public class UserApi {
 4     @Autowired
 5     UserService userService;
 6     @Autowired
 7     TokenService tokenService;
 8     //Sign in
 9     @PostMapping("/login")
10     public Object login(@RequestBody User user){
11         JSONObject jsonObject=new JSONObject();
12         User userForBase=userService.findByUsername(user);
13         if(userForBase==null){
14             jsonObject.put("message","Login failed,user does not exist");
15             return jsonObject;
16         }else {
17             if (!userForBase.getPassword().equals(user.getPassword())){
18                 jsonObject.put("message","Login failed,Password error");
19                 return jsonObject;
20             }else {
21                 String token = tokenService.getToken(userForBase);
22                 jsonObject.put("token", token);
23                 jsonObject.put("user", userForBase);
24                 return jsonObject;
25             }
26         }
27     }
28     @UserLoginToken
29     @GetMapping("/getMessage")
30     public String getMessage(){
31         return "You have passed the verification";
32     }
33 }

If there is no annotation, it is not verified by default, and the login interface is generally not verified. In getMessage(), I added the login annotation, indicating that the interface must log in to get token, and then add token in the request header and can be accessed only after verification

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

Accessing api/getMessage interface without token

 
              image.png

I use unified exception handling here, so I only see the error message

 

Next, log in to get the token

 
image.png

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

 

Add token to the request header and visit the api/getMessage interface again

 
image.png

Note: the key here must not be wrong, because the value of the keyword token is taken in the interceptor
String token = httpServletRequest.getHeader("token");
After the token is added, it can pass the verification and interface access smoothly

 

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









Keywords: Java github JSON Spring

Added by VirusDoctor on Fri, 17 Jan 2020 09:44:12 +0200