2

Transfer from: Pillow Book

Link: https://zhenbianshu.github.io/

The article introduces four ways to implement universal auth in spring-boot, including traditional AOP, interceptors, parameter parsers and filters, and provides corresponding example codes, and finally briefly summarizes their execution order.

Foreword

Recently, I have been overwhelmed by endless business needs. I didn’t have time to breathe. I finally received a job that allowed me to break through the comfort zone of the code. The process of solving it was very tortuous. It made me doubt my life at one time, but the gain was also great. Obviously, but I feel that I have erased the layer of yarn that Java, Tomcat, and Spring have been blocking my eyes. The understanding of them has taken a new level. I haven't output for a long time, so I pick one aspect to summarize, hoping to learn more about other things during the combing process.

Due to the prosperous ecology of Java, there are a large number of articles dedicated to each module below. So I chose another angle, starting from practical problems, and connecting these scattered knowledge together, you can look at it as a summary. For the ultimate detailed introduction of each module, you can go to the official document or read other blogs on the Internet. The requirements are very simple and clear. They are not at all the same as the glamorous requirements of the products: add a universal appkey whitelist verification function to our web framework, hoping that it will be more extensible.

This web framework is implemented by the pioneer of the department based on spring-boot. It is between the business and the Spring framework. It does some general functions that are biased towards the business, such as log output, function switches, and general parameter analysis. It is usually transparent to the business. Recently, I have been busy with the requirements and the code, and I have never even noticed its existence.

Traditional AOP

For this kind of demand, the first thing that comes to mind is of course the AOP interface provided by Spring-boot. You only need to add a pointcut before the Controller method, and then process the pointcut.

implements

Its use steps are as follows:

  1. Use @Aspect declare the aspect class WhitelistAspect ;
  2. Add a cut point whitelistPointcut() in the aspect class. In order to realize the flexible and assembleable ability of this cut point, we do not use execution intercept all, but add an annotation @Whitelist , and the annotated method will check the whitelist.
  3. Use spring's AOP annotation @Before in the aspect class to declare a notification method checkWhitelist() to verify the whitelist before the Controller method is executed.

The pseudo-code of the aspect class is as follows:

@Aspect
public class WhitelistAspect {
   
 @Before(value = "whitelistPointcut() && @annotation(whitelist)")
 public void checkAppkeyWhitelist(JoinPoint joinPoint, Whitelist whitelist) {
     checkWhitelist();
     // 可使用 joinPoint.getArgs() 获取Controller方法的参数
     // 可以使用 whitelist 变量获取注解参数
 }
   
   
 @Pointcut("@annotation(com.zhenbianshu.Whitelist)")
 public void whitelistPointCut() {
 }
}

@Whitelist annotation to the Controller method to realize the function.

extension

In this example, annotations are used to declare the point of cut, and I have realized the whitelist to be verified through the annotation parameters. If you need to add other whitelists later, such as verifying by UID, you can add this annotation uid() and other methods to achieve custom verification. In addition, spring's AOP also supports declaration methods such as 161d2bb1c3fdcd execution (execution method), bean (execution method of a Bean object matching a specific name) @Around (executed in the execution of the target function), @After (after the method is executed) ) And other notification methods. In this way, the function has been realized, but the leader is not satisfied with it=\_=. The reason is that AOP is used too much in the project, and it is all overused. I suggest that I change it. Well, I had to do it.

Interceptor

Spring's Interceptor is also very suitable for this function. As the name implies, the interceptor is used to determine whether to execute this method through some parameters before the Action in the Controller is executed. To implement an interceptor, you can implement Spring's HandlerInterceptor interface.

implements

The implementation steps are as follows:

  1. Define the interceptor class AppkeyInterceptor and implement the HandlerInterceptor interface.
  2. Implement its preHandle() method;
  3. Use annotations and parameters in the preHandle method to determine whether the request needs to be intercepted. When the request is intercepted, the interface returns false ;
  4. Register this interceptor in the custom WebMvcConfigurerAdapter

AppkeyInterceptor categories are as follows:

@Component
public class WhitelistInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        Whitelist whitelist = ((HandlerMethod) handler).getMethodAnnotation(Whitelist.class);
        // whitelist.values(); 通过 request 获取请求参数,通过 whitelist 变量获取注解参数
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
  // 方法在Controller方法执行结束后执行
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
  // 在view视图渲染完成后执行
    }
}

extension

To enable the interceptor, you must explicitly configure it to enable it. Here we use WebMvcConfigurerAdapter to configure it. It should be noted that the MvcConfiguration that inherits it needs to be in the ComponentScan path.

@Configuration
public class MvcConfiguration extends WebMvcConfigurerAdapter {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new WhitelistInterceptor()).addPathPatterns("/*").order(1);
        // 这里可以配置拦截器启用的 path 的顺序,在有多个拦截器存在时,任一拦截器返回 false 都会使后续的请求方法不再执行
    }
}

It should also be noted that after the interceptor is executed successfully, the response code is 200 , but the response data is empty. Spring Boot basic tutorial and sample source code can be learned here: https://github.com/javastacks/javastack, it is very complete. After using the interceptor to implement the function, the leader finally made a big move: we already have an Auth parameter, and the appkey can be obtained from the Auth parameter. You can use the presence or absence as a method of Auth, why not Auth Time check? emmm... vomiting blood.

ArgumentResolver

The parameter parser is a tool provided by Spring for parsing custom parameters. Our commonly used @RequestParam annotation has its shadow. Using it, we can combine the parameters into what we want before entering the Controller Action. Spring will maintain a ResolverList . When the request arrives, Spring finds that there are custom type parameters (non-basic types), and will try these Resolvers in turn until there is a Resolver that can resolve the required parameters. To implement a parameter parser, you need to implement the HandlerMethodArgumentResolver interface.

implements

  1. Define the custom parameter type AuthParam , there are appkey related fields in the class;
  2. Define AuthParamResolver and implement the HandlerMethodArgumentResolver interface;
  3. Implement the supportsParameter() interface method to adapt AuthParam and AuthParamResolver;
  4. Implement the resolveArgument() interface method to parse the reqest object to generate an AuthParam object, and verify the AuthParam here to confirm whether the appkey is in the whitelist;
  5. Add the AuthParam parameter in the signature of the Controller Action method to enable this Resolver;

The implemented AuthParamResolver class is as follows:

@Component
public class AuthParamResolver implements HandlerMethodArgumentResolver {

    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return parameter.getParameterType().equals(AuthParam.class);
    }

    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        Whitelist whitelist = parameter.getMethodAnnotation(Whitelist.class);
        // 通过 webRequest 和 whitelist 校验白名单
        return new AuthParam();
    }
}

extension

Of course, using the parameter parser also needs to be configured separately, we also configure it in WebMvcConfigurerAdapter :

@Configuration
public class MvcConfiguration extends WebMvcConfigurerAdapter {

    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
        argumentResolvers.add(new AuthParamResolver());
    }
}

This time the implementation is over, I am still a little worried, so I searched the Internet whether there are other ways to achieve this function, and found that the common one is Filter .

Filter

Filter is not provided by Spring. It is defined in the Servlet specification and supported by the Servlet container. Requests filtered by Filter will not be dispatched to the Spring container. Its realization is also relatively simple, just realize the javax.servlet.Filter interface. Because it is not in the Spring container, Filter cannot obtain the resources of the Spring container, and can only use the native Java ServletRequest and ServletResponse to obtain request parameters. In addition, it is necessary to display the doFilter method of the FilterChain in a Filter, otherwise the request is considered to be intercepted. The implementation is similar:

public class WhitelistFilter implements javax.servlet.Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
  // 初始化后被调用一次
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
     // 判断是否需要拦截
       chain.doFilter(request, response); // 请求通过要显示调用
    }

    @Override
    public void destroy() {
     // 被销毁时调用一次
    }
}

extension

Filter also needs to display configuration:

@Configuration
public class FilterConfiguration {

    @Bean
    public FilterRegistrationBean someFilterRegistration() {
        FilterRegistrationBean registration = new FilterRegistrationBean();
        registration.setFilter(new WhitelistFilter());
        registration.addUrlPatterns("/*");
        registration.setName("whitelistFilter");
        registration.setOrder(1); // 设置过滤器被调用的顺序
        return registration;
    }
}

Summary

The four implementation methods have their own suitable scenarios, so what is the calling sequence among them? Filter is implemented by Servlet, which is naturally the first to be called. Subsequent to be called Interceptor is intercepted. Naturally, there is no need for subsequent processing, then the parameter parser, and finally the point of cut of the aspect.

recommends 2 original springboot+Vue projects, with complete video explanation, documentation and source code:

【VueAdmin】Teach you how to develop SpringBoot+Jwt+Vue's front-end and back-end separation management system

[VueBlog] Complete teaching of front-end and back-end separation blog project based on SpringBoot+Vue development

If you have any questions, [161d2bb1c40319 Java Q&A ] to ask me


MarkerHub
538 声望246 粉丝