After customizing the starter, a line of code can realize JWT login authentication, which is amazing

Time:2020-11-17

In the last article《Spring boot integrates JWT to realize user login authentication》We have encapsulated a JWT based on nimbus Jose JWT, and we only need to customize handlerinterceptor implementation class and webmvcconfigurer implementation class to introduce JWT for login authentication in the project.

The starter in spring boot is a very important mechanism. It can discard the previous complicated configuration and integrate it into the starter. The application only needs to introduce starter dependency in maven, and spring boot can automatically scan the information to be loaded and start the corresponding default configuration. Starter lets us get rid of the trouble of processing various dependent libraries and configuring various information. Spring boot will automatically discover the required beans through the classes in the classpath path and register them into the IOC container. Spring boot provides spring boot starter dependency modules for various scenarios of daily enterprise application development. All of these dependency modules follow the default configuration of conventions, and allow us to adjust these configurations, that is, to follow the concept of “Convention is greater than configuration”.

Next, we will customize a starter on the basis of Ron JWT encapsulated before. As long as the starter is introduced into the dependency and simple configuration is defined, JWT can be used to implement login authentication.

Create new projects and configure dependencies

The starter provided by spring bootspring-boot-starter-xxxThe way to name it. It is recommended to use custom starterxxx-spring-boot-starterNaming rules to distinguish starters provided by the spring boot ecosystem. So we build a new project Ron JWT spring boot starter.

After customizing the starter, a line of code can realize JWT login authentication, which is amazing

In pom.xml Introducing dependencies in

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-configuration-processor</artifactId>
  <optional>true</optional>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
  <groupId>io.ron</groupId>
  <artifactId>ron-jwt</artifactId>
  <version>1.0-SNAPSHOT</version>
</dependency>

The main function of spring boot configuration processor is to generate spring configuration under meta-inf at compile time- metadata.json This file is mainly used by IDE, that is, you can use the application.properties In the file, click Ctrl + to enter the class where the configuration attribute is located.

The main function of spring boot autoconfigure is to provide automatic assembly function.

Spring boot starter web is because we will have built-in handlerinterceptor implementation class and webmvcconfigurer implementation class.

Ron JWT is the JWT library that we packaged in the last article.

Define configuration item management class

We define jwtproperties to declare which configuration items users of the starter can use.

@ConfigurationProperties(prefix = "ron.jwt")
public class JwtProperties {

    private String tokenName = JwtUtils.DEFAULT_TOKEN_NAME;

    private String hmacKey;

    private String jksFileName;

    private String jksPassword;

    private String certPassword;

    //Issued by
    private String issuer;

    //Theme
    private String subject;

    //Audience
    private String audience;

    private long notBeforeIn;

    private long notBeforeAt;

    private long expiredIn;

    private long expiredAt;
}

For specific parameter description, please refer to jwtconfig in the previous article.

@ConfigurationPropertiesThe annotation specifies that all configuration items are prefixed withron.jwt

@ConfigurationPropertiesThe basic usage of is very simple: we provide a class with fields for each external property to capture. Please note the following:

  • The prefix defines which external properties will be bound to the fields of the class.
  • According to spring boot’s loose binding rules, the property name of a class must match the name of an external property.
  • We can simply initialize a field with a value to define a default value.
  • The class itself can be package private.
  • Class’s fields must have a public setter method.

Relaxed binding rules of spring boot:

Spring boot uses some loose binding property rules. Therefore, the following variants are bound to the tokenname property:

  • ron.jwt.tokenname=Authorization
  • ron.jwt.tokenName=Authorization
  • ron.jwt.token_name=Authorization
  • ron.jwt.token-name=Authorization

Realize related functions

In the last article, we implemented the handlerinterceptor implementation class and the webmvcconfigurer implementation class in a specific business project. In fact, this part is more general logic in the project, so we consider putting these implementations in the starter. In the project, you can use JWT authentication function directly by introducing starter without unnecessary customization.

JwtInterceptor

public class JwtInterceptor implements HandlerInterceptor {

    private Logger logger = LoggerFactory.getLogger(JwtInterceptor.class);

    private static final String PREFIX_BEARER = "Bearer ";

    @Autowired
    private JwtProperties jwtProperties;

    @Autowired
    private JwtService jwtService;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
                             Object handler) throws Exception {

        //If it is not mapped to a method, it passes through the
        if(!(handler instanceof HandlerMethod)){
            return true;
        }

        HandlerMethod handlerMethod = (HandlerMethod) handler;
        Method method = handlerMethod.getMethod();
        //Check whether there is @ authrequired annotation. If yes and required() is false, skip it
        if (method.isAnnotationPresent(AuthRequired.class)) {
            AuthRequired authRequired = method.getAnnotation(AuthRequired.class);
            if (!authRequired.required()) {
                return true;
            }
        }

        String token = request.getHeader(jwtProperties.getTokenName());

        logger.info("token: {}", token);

        if (StringUtils.isEmpty(token) || token.trim().equals(PREFIX_BEARER.trim())) {
            return true;
        }

        token = token.replace(PREFIX_BEARER, "");

        //Set token in thread local variable
        JwtContext.setToken(token);

        //In the thread local variables, set the real data passed, such as the current user information
        String payload = jwtService.verify(token);
        JwtContext.setPayload(payload);

        return onPreHandleEnd(request, response, handler, payload);
    }

    public boolean onPreHandleEnd(HttpServletRequest request, HttpServletResponse response,
                                  Object handler, String payload) throws Exception {
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response,
                           Object handler, ModelAndView modelAndView) throws Exception {

    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
                                Object handler, Exception ex) throws Exception {
        //Be sure to clean up thread local variables before the end of the thread
        JwtContext.removeAll();
    }
}

onPreHandleEndMethod is a default implementation. If necessary in business, you can also inherit jwtinterceptor and add custom logic to this method. One possible scenario is to put the JWT token into redis for timeout management.

JwtInterceptorConfig

public class JwtInterceptorConfig implements WebMvcConfigurer {

    private JwtInterceptor jwtInterceptor;

    public JwtInterceptorConfig(JwtInterceptor jwtInterceptor) {
        this.jwtInterceptor = jwtInterceptor;
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(jwtInterceptor).addPathPatterns("/**");
    }
}

All requests are blocked by default. In the last article, we mentioned that we can cooperate@AuthRequiredTo filter requests that do not need to be intercepted.

Write auto configuration logic

JwtAutoConfiguration

@Configuration
@EnableConfigurationProperties(JwtProperties.class)
public class JwtAutoConfiguration {

    @Autowired
    private JwtProperties jwtProperties;

    @Bean
    public JwtConfig jwtConfig() {
        JwtConfig jwtConfig = new JwtConfig();
        BeanUtils.copyProperties(jwtProperties, jwtConfig);
        return jwtConfig;
    }

    @Bean
    public JwtService jwtService() {
        JwtConfig jwtConfig = jwtConfig();
        return JwtUtils.obtainJwtService(jwtConfig);
    }

    @Bean
    public JwtInterceptor jwtInterceptor() {
        return new JwtInterceptor();
    }

    @Bean
    public JwtInterceptorConfig jwtInterceptorConfig() {
        return new JwtInterceptorConfig(jwtInterceptor());
    }
}

@EnableConfigurationPropertiesThe function of@ConfigurationPropertiesAnnotate the class and make it effective.

@EnableConfigurationPropertiesIt is explained in the document that when@EnableConfigurationPropertiesAnnotations apply to your@ConfigurationWhen, any@ConfigurationPropertiesThe annotated beans are automatically configured by the environment property. This style of configuration is particularly suitable for use with spring application’s external yaml configuration.

Integrate starter to make it effective

There are two ways to make the starter work in an application.

Loading through SPI mechanism – passive effect

Through spring bootSPITo load our starter.

Create a new WEB-INF in the resources directory/ spring.factories Documents.

META-INF/ spring.factories File is the core file for spring boot framework to identify and parse starter. spring.factories The file is a spring container that helps beans outside the spring boot project package (that is, add beans in the dependency in the POM file) to register with the spring boot project. because@ComponentScanAnnotations can only scan beans in the spring boot project package and register them in the spring container, so you need to@EnableAutoConfigurationAnnotation to register beans outside the project package. And spring.factories File is used to record the bean class names that need to be registered outside the project package.

org.springframework.boot.autoconfigure.EnableAutoConfiguration=io.ron.jwt.starter.JwtAutoConfiguration

Custom enable annotation import – active

When the starter component is integrated into our spring boot application, we need to actively declare and enable the starter to take effect. We can customize a@EnableComments are then passed through the auto configuration classImportAnnotations are introduced.

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import({JwtAutoConfiguration.class})
@Documented
@Inherited
public @interface EnableJwt {

}

If the active method is used, the passive meta-inf will be implemented/ spring.factories The file needs to be removed.

Package and publish starter

usemvn installCan be packaged and installed locally;

usemvn deployCan be published to a remote repository.

In the future, I will write an article about maven, combing with my years of experience in the industry, and combing out the Maven skills that developers will use.

Welcome to my official account: Java (ID:craft4j)。

Test application

You can reuse the project from the previous article in pom.xml Modify the dependency and introduce Ron JWT spring boot starter.

<dependency>
  <groupId>io.ron</groupId>
  <artifactId>ron-jwt-spring-boot-starter</artifactId>
  <version>1.0-SNAPSHOT</version>
</dependency>

Remove the handlerinterceptor implementation class and the webmvcconfigurer implementation class.

In application.yml Medium configurationron.jwt.hmac-keyTo provide the JWT signature and verification function of HMAC algorithm.

If the starter takes a passive approach, you can now run the program, then use postman to test and observe the results.

If the starter takes the active mode, you need to add it to the project startup class@EnableJwtThe annotation introduces JWT starter.

@SpringBootApplication
public class JwtStarterApplication {

    public static void main(String[] args) {
        SpringApplication.run(JwtStarterApplication.class, args);
    }
}

– End –


Through two articles, we show the use of the JWT Library Based on nimbus Jose JWT. On this basis, we encapsulate our own basic JWT library, and introduce the steps of custom starter of spring boot and the implementation of custom @ enable annotation.

If you feel rewarding, please pay attention to my official account: Java (ID:craft4j)The first time to get the knowledge dynamic of Java back-end and architecture.

If you are interested in the full source code of the project, you can reply in the official account.jwtTo get.