4 ways Spring Boot implements universal Auth authentication!

The article introduces four ways to implement general Auth in Spring Boot, including traditional AOP, interceptor, parameter parser and filter, and provides corresponding example codes. Finally, it briefly summarizes their execution order for your reference and study .

| Foreword

Recently, I have been overwhelmed by endless business needs and have no time to breathe. I finally received a job that allowed me to break through the comfort zone of code. The process of solving it was very tortuous. Obviously, but I feel that I have erased the layer of veil that java, Tomcat, and Spring have been blocking my eyes. A new level of understanding of them has been achieved.

I haven't output for a long time, so I pick one aspect to summarize, hoping to learn some other things in the process of sorting out. Due to the prosperous ecology of Java, each of the following modules has a large number of articles dedicated to it. 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 documentation or read other blogs on the Internet.

The requirements are simple and clear, and are not at all the same as the coquettish requirements raised by the products: add an appkey whitelist verification function to our web framework 通用, and hope that its scalability will be better.

This web framework is implemented by the pioneers 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 making the requirements and writing the code well, and I have never even noticed its existence.

| Method 1. Traditional AOP

For this requirement, the first thing that comes to mind is of course the AOP interface provided by Spring Boot. You only need to add a cut point before the Controller method, and then process the cut point.

accomplish

Its use steps are as follows:

  1. Use  @Aspect to declare the aspect class  WhitelistAspect;

  2. Add a cut point in the aspect class  whitelistPointcut(). In order to realize the flexible and configurable ability of this cut point, instead of using  execution all interception, add an annotation  @Whitelist, and the annotated method will check the whitelist.

  3. Use spring's AOP annotations in the aspect class to declare  @Before a notification method  checkWhitelist() to check the whitelist before the Controller method is executed.

The pseudo code of the facet 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() {

    }

}

Add annotations to the Controller method  @Whitelist to implement the function.

expand

In this example, annotations are used to declare the pointcut, and I have implemented the whitelist to be verified through annotation parameters. If you need to add other whitelists later, such as UID to verify, you can add this annotation  uid() and other methods to implement custom verification.

In addition, spring's AOP also supports  execution(执行方法) 、bean(匹配特定名称的 Bean 对象的执行方法)equal pointcut declaration methods and  @Around(在目标函数执行中执行) 、@After(方法执行后) equal notification methods.

In this way, the function has been realized, but the leader is not satisfied. The reason is that AOP is used too much in the project, and it is overused. I suggest another way. Well, I had to start it.

| Method 2: Using Interceptor

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

accomplish

The implementation steps are as follows:

  1. Define the interceptor class  AppkeyInterceptor and implement the HandlerInterceptor interface.

  2. implement its  preHandle() method;

  3. In the preHandle method, judge whether the request needs to be intercepted through annotations and parameters, and the interface returns when the request is intercepted  false;

  4. WebMvcConfigurerAdapter Register this interceptor in a custom  class;

AppkeyInterceptor The class is 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视图渲染完成后执行
    }
}

expand

To enable the interceptor, you must explicitly configure it to enable it. Here we use  WebMvcConfigurerAdapter it to configure it. It should be noted that those who inherit it  MvcConfiguration need to be under 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 executes successfully, the response code is  200, but the response data is empty.

After using the interceptor to realize the function, the leader finally made a big move: we already have an Auth parameter, the appkey can be obtained from the Auth parameter, and whether it is in the whitelist can be used as a way of Auth, why not when Auth check? emmm... vomiting blood.

| Method 3: Use ArgumentResolver

The parameter parser is a tool provided by Spring for parsing custom parameters. Our commonly used  @RequestParam annotations have its shadow. Using it, we can combine the parameters into what we want before entering the Controller Action.

Spring will maintain one  ResolverList. When the request arrives, Spring finds that there are custom type parameters (non-basic types), and will try these Resolvers in turn until a Resolver can resolve the required parameters. To implement a parameter parser, you need to implement  HandlerMethodArgumentResolver the interface.

accomplish

  1. Define a custom parameter type  AuthParam, and there are appkey related fields in the class;

  2. Define  AuthParamResolver and implement the HandlerMethodArgumentResolver interface;

  3. Implement  supportsParameter() the interface method to adapt AuthParam and AuthParamResolver;

  4. Implement  resolveArgument() the interface method to parse the reqest object to generate the AuthParam object, and check the AuthParam here to confirm whether the appkey is in the whitelist;

  5. Add the AuthParam parameter in the signature on 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();
    }
}

expand

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

@Configuration
public class MvcConfiguration extends WebMvcConfigurerAdapter {

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

After the realization this time, I am still a little worried, so I searched online to see if there are other ways to realize this function, and found that there are other common ones  Filter.

| Method 4: Use 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 implementation is also relatively simple,  javax.servlet.Filter just implement the interface.

Since it is not in the Spring container, Filter cannot obtain the resources of the Spring container, and can only use native Java ServletRequest and ServletResponse to obtain request parameters.

In addition, in a Filter, the doFilter method of FilterChain must be called explicitly, otherwise the request is considered to be intercepted. Implement something like:

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() {
        // 被销毁时调用一次
    }
}

expand

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 implementations have their suitable scenarios, so what is the calling sequence between them?

Filter is implemented by Servlet. Naturally, it is called first, and the interceptor is called later. Naturally, it does not need to be processed later, then the parameter parser, and finally the cutting point of the aspect.

For more content, please pay attention to the official account [Programmer Style] to get more exciting content!

Guess you like

Origin blog.csdn.net/dreaming317/article/details/130120925