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:
-
Use
@Aspect
to declare the aspect classWhitelistAspect
; -
Add a cut point in the aspect class
whitelistPointcut()
. In order to realize the flexible and configurable ability of this cut point, instead of usingexecution
all interception, add an annotation@Whitelist
, and the annotated method will check the whitelist. -
Use spring's AOP annotations in the aspect class to declare
@Before
a notification methodcheckWhitelist()
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:
-
Define the interceptor class
AppkeyInterceptor
and implement the HandlerInterceptor interface. -
implement its
preHandle()
method; -
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
; -
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
-
Define a custom parameter type
AuthParam
, and there are appkey related fields in the class; -
Define
AuthParamResolver
and implement the HandlerMethodArgumentResolver interface; -
Implement
supportsParameter()
the interface method to adapt AuthParam and AuthParamResolver; -
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; -
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 WebMvcConfigurerAdapter
internally :
@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.