This article understands how filters and interceptors work through a simple security certification example development practice.
Many articles associate filters, interceptors and listeners with Spring to explain, and believe that filters, interceptors and listeners are widely used component functions provided by Spring.
But strictly speaking, filters and listeners belong to the Servlet API and have nothing to do with Spring.
Because the filter inherits from the javax.servlet.Filter interface and the listener inherits from the javax.servlet.ServletContextListener interface, only the interceptor inherits the org.springframework.web.servlet.HandlerInterceptor interface.
The above flowchart is referenced from the online information, and one picture is worth a thousand words. After reading this article, you will have a deeper understanding of the calling process of filters and interceptors.
1. Safety certification design ideas
Sometimes, when internal and external networks call APIs, the security requirements for different requirements. In many cases, the various restrictions on external networks call APIs are not necessary in the intranet. However, when deploying gateways, the APIs to be called by internal and external networks may be deployed together due to cost and complexity issues.
To realize the security of the REST interface, it can be done through mature frameworks such as Spring Security or shiro.
However, because security frameworks are often complex (I counted Spring Security, there are about 11 core modules, and the amount of source code of shiro is also quite amazing). At the same time, complex configurations may be introduced (can it make people feel more enjoyable), which is not conducive to the flexible and rapid development, deployment and problem investigation of small and medium-sized teams.
Many teams build their own wheels to achieve safety certification. This simple certification example in this article refers to the former factory development team where I am, and can be considered a token-based security certification service.
The general design idea is as follows:
1. Customize the http request header. Each time the API is called, a token value is passed in the request header.
2. Place the token in the cache (such as redis), and set the expiration time of different policies according to different business and APIs.
3. Token can set whitelists and blacklists, which can limit the frequency of API calls, facilitate development and testing, facilitate emergency handling of abnormalities, and even temporarily close the API.
4. The external network call must be sent token. The token can be related to the user, such as each time you open the page or log in to generate a token write request header, the page verifys the validity of cookies and tokens, etc.
There are two concepts in the Spring Security framework, namely authentication and authorization . Authentication refers to users who can access the system, while authorization is a resource that users can access.
To achieve the above simple security authentication requirements, you may need to independently create a token service to ensure that the token is globally unique. The modules that may include custom flow generators, CRM, encryption and decryption, logs, API statistics, caches, etc., but they are actually weakly bound to users (CRM). Some public services that are related to users, such as SMS and email services that we often use, can also solve security calls through the token mechanism.
In summary, the simple security certification in this article is actually a bit different from the authentication and authorization provided by the Spring Security framework. Of course, this "safety" treatment method is not new to professionals, but it can block a large number of novice users from the outside.
2. Customize Filter
Similar to Spring MVC, Spring Boot provides many servlet filters (Filters) to use, and it automatically adds some commonly used filters, such as CharacterEncodingFilter (used to handle encoding problems), HiddenHttpMethodFilter (hidden HTTP function), HttpPutFormContentFilter (form form processing), RequestContextFilter (request context), etc. Usually we will also customize Filter to implement some common functions, such as recording logs, determining whether to log in, permission verification, etc.
1. Custom request header
It's very simple, add a custom request header authtoken in the request header:
@RequestMapping(value = "/getinfobyid", method = RequestMethod.POST) @ApiOperation("Query product information based on product Id") @ApiImplicitParams({ @ApiImplicitParam(paramType = "header", name = "authtoken", required = true, value = "authtoken", dataType = "String"), }) public GetGoodsByGoodsIdResponse getGoodsByGoodsId(@RequestHeader String authtoken, @RequestBody GetGoodsByGoodsIdRequest request) { return _goodsApiService.getGoodsByGoodsId(request); }getGoodsByGoodsIdThe authtoken field modified by @RequestHeader can be displayed under a framework like swagger.
After calling, you can see the request header according to the http tool. The example in this article is authtoken (different from the token of some frameworks):
Note: Many httpclient tools support dynamic transmission request headers, such as RestTemplate.
2. Implement Filter
There are three methods in the Filter interface, namely init, doFilter and destory. When you see the name, you will probably know their main uses. Usually, we just need to process Http requests within the doFilter method:
package com.power.demo.controller.filter;import com.power.demo.common.AppConst;import com.power.demo.common.BizResult;import com.power.demo.service.contract.AuthTokenService;import com.power.demo.util.PowerLogger;import com.power.demo.util.SerializeUtil;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Component;import javax.servlet.*;import javax.servlet.http.HttpServletRequest;import java.io.IOException;@Componentpublic class AuthTokenFilter implements Filter { @Autowired private AuthTokenService authTokenService; @Override public void init(FilterConfig var1) throws ServletException { } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) request; String token = req.getHeader(AppConst.AUTH_TOKEN); BizResult<String> bizResult = authTokenService.powerCheck(token); System.out.println(SerializeUtil.Serialize(bizResult)); if (bizResult.getIsOK() == true) { PowerLogger.info("auth token filter passed"); chain.doFilter(request, response); } else { throw new ServletException(bizResult.getMessage()); } } @Override public void destroy() { }}AuthTokenFilterNote that from the perspective of actual hierarchy, most of the things that process more expression layers are processed. It is not recommended to directly use the data access layer in Filter. Although I have seen such code many times in many old antique projects a year or two ago, and there are precedents for writing this way in the book <<Spring Practical>>.
3. Certification Service
This is the main business logic. The sample code is just a simple way to write down ideas, and should not be used easily in production environments:
package com.power.demo.service.impl;import com.power.demo.cache.PowerCacheBuilder;import com.power.demo.common.BizResult;import com.power.demo.service.contract.AuthTokenService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Component;import org.springframework.util.StringUtils;@Componentpublic class AuthTokenServiceImpl implements AuthTokenService { @Autowired private PowerCacheBuilder cacheBuilder; /* * Verify whether the request header token is legal* */ @Override public BizResult<String> powerCheck(String token) { BizResult<String> bizResult = new BizResult<>(true, "Verification passed"); System.out.println("token's value is:" + token); if (StringUtils.isEmpty(token) == true) { bizResult.setFail("authtoken is empty"); return bizResult; } //Processing blacklist bizResult = checkForbidList(token); if (bizResult.getIsOK() == false) { return bizResult; } //Processing whitelist bizResult = checkAllowList(token); if (bizResult.getIsOK() == false) { return bizResult; } String key = String.format("Power.AuthTokenService.%s", token); //cacheBuilder.set(key, token); //cacheBuilder.set(key, token.toUpperCase()); //Fetch String existToken from the cache = cacheBuilder.get(key); if (StringUtils.isEmpty(existToken) == true) { bizResult.setFail(String.format("This authtoken:%s", token)); return bizResult; } //Compare whether the token is the same Boolean isEqual = token.equals(existToken); if (isEqual == false) { bizResult.setFail(String.format("Illegal authtoken:%s", token)); return bizResult; } //do something return bizResult; }}AuthTokenServiceImplYou can refer to the cache service you use here, which is also a summary of my experience in the previous factory.
4. Register Filter
There are two common ways to write:
(1) Use the @WebFilter annotation to identify Filter
@Order(1)@WebFilter(urlPatterns = {"/api/v1/goods/*", "/api/v1/userinfo/*"})public class AuthTokenFilter implements Filter {Using @WebFilter annotation, you can also use @Order annotation in conjunction with the @Order annotation. The @Order annotation represents the order of filtering. The smaller the value, the more you execute it first. This Order size is as useful as processing the life cycle of HTTP requests during our programming process. Of course, if the Order is not specified, the order of the filter is called opposite to the order of the added filters, and the implementation of the filter is the responsibility chain pattern.
Finally, add the @ServletComponentScan annotation to the startup class to use the custom filter normally.
(2) Use FilterRegistrationBean to customize Filter registration
This article uses the second implementation to implement custom Filter registration:
package com.power.demo.controller.filter;import com.google.common.collect.Lists;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.web.servlet.FilterRegistrationBean;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.stereotype.Component;import java.util.List;@Configuration@Componentpublic class RestFilterConfig { @Autowired private AuthTokenFilter filter; @Bean public FilterRegistrationBean filterRegistrationBean() { FilterRegistrationBean registrationBean = new FilterRegistrationBean(); registrationBean.setFilter(filter); //Set (fuzzy) matching url List<String> urlPatterns = Lists.newArrayList(); urlPatterns.add("/api/v1/goods/*"); urlPatterns.add("/api/v1/userinfo/*"); registrationBean.setUrlPatterns(urlPatterns); registrationBean.setOrder(1); registrationBean.setEnabled(true); return registrationBean; }}RestFilterConfigPlease pay special attention to urlPatterns. The attribute urlPatterns specifies the URL pattern to be filtered. This parameter is of great significance for the Filter area of action.
Register Filter, and when Spring Boot starts, it will automatically add the filter call chain ApplicationFilterChain when it detects a bean with javax.servlet.Filter.
Call an API to try the effect:
Usually, we customize a global unified exception management enhancement GlobalExceptionHandler under Spring Boot (it will be slightly different from the above).
According to my practice, exceptions thrown in the filter will not be caught and processed by the globally unique exception management enhancement. This is different from the interceptor Inteceptor and the custom AOP interceptor introduced in the next article.
At this point, a simple security authentication service implemented through custom Filter is done.
3. Custom interceptor
1. Implement the interceptor
Inherit the interface HandlerInterceptor and implement the interceptor. The interface methods are as follows:
preHandle is executed before request execution
postHandle is the end of the request execution
AfterCompletion is executed after the rendering of the view is completed
package com.power.demo.controller.interceptor;import com.power.demo.common.AppConst;import com.power.demo.common.BizResult;import com.power.demo.service.contract.AuthTokenService;import com.power.demo.util.PowerLogger;import com.power.demo.util.SerializeUtil;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Component;import org.springframework.web.servlet.HandlerInterceptor;import org.springframework.web.servlet.ModelAndView;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;/* * Authentication token interceptor* */@Componentpublic class AuthTokenInterceptor implements HandlerInterceptor { @Autowired private AuthTokenService authTokenService; /* * Execute before request execution* */ @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { boolean handleResult = false; String token = request.getHeader(AppConst.AUTH_TOKEN); BizResult<String> bizResult = authTokenService.powerCheck(token); System.out.println(SerializeUtil.Serialize(bizResult)); handleResult = bizResult.getIsOK(); PowerLogger.info("auth token interceptor interceptor interceptor interceptor interceptor interceptor interceptor interceptor interceptor passed"); } else { throw new Exception(bizResult.getMessage()); } return handleResult; } /* * The request ends execution* */ @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { } /* * Execute after view rendering is completed* */ @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { }}AuthTokenInterceptorIn the example, we choose to perform token security authentication before the request is executed.
The authentication service is the AuthTokenService introduced in the filter, and the business logic layer is reused.
2. Register the interceptor
Define an InterceptorConfig class, inherited from WebMvcConfigurationSupport, and the WebMvcConfigurerAdapter is outdated.
Inject AuthTokenInterceptor as bean, the URLs and filters that other settings interceptor intercept are very similar:
package com.power.demo.controller.interceptor;import com.google.common.collect.Lists;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.stereotype.Component;import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;import org.springframework.web.servlet.config.annotation.InterceptorRegistry;import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;import java.util.List;@Configuration@Componentpublic class InterceptorConfig extends WebMvcConfigurationSupport { //WebMvcConfigurerAdapter is outdated private static final String FAVICON_URL = "/favicon.ico"; /** * Found that if WebMvcConfigurationSupport is inherited, the relevant content configured in yml will be invalid. * * @param registry */ @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/").addResourceLocations("/**"); registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/"); } /** * Configure servlet processing*/ @Override public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configure) { configurer.enable(); } @Override public void addInterceptors(InterceptorRegistry registry) { //Set (fuzzy) matching url List<String> urlPatterns = Lists.newArrayList(); urlPatterns.add("/api/v1/goods/*"); urlPatterns.add("/api/v1/userinfo/*"); registry.addInterceptor(authTokenInterceptor()).addPathPatterns(urlPatterns).excludePathPatterns(FAVICON_URL); super.addInterceptors(registry); } //Write the interceptor as a bean into the configuration @Bean public AuthTokenInterceptor authTokenInterceptor() { return new AuthTokenInterceptor(); }}InterceptorConfigAfter starting the application, you can see the effect of interceptor interception by calling the interface. Global Unified Exception Management GlobalExceptionHandler handles the following exceptions after catching them:
It is almost the same as the main error message displayed by the filter, but the stack information is richer.
4. The difference between filter and interceptor
The main differences are as follows:
1. Interceptors are mainly based on Java's reflection mechanism, while filters are based on function callbacks
2. Interceptor does not depend on servlet container, filters rely on servlet container
3. Interceptors can only work on action requests, while filters can work on almost all requests.
4. The interceptor can access the object in the action context and value stack, but the filter cannot access it.
5. During the life cycle of an action, the interceptor can be called multiple times, while the filter can only be called once when the container is initialized.
Some articles I have referenced said that "the interceptor can obtain various beans in the IOC container, but the filter cannot. This is very important. Injecting a service into the interceptor can call business logic." After actual verification, this is wrong.
Note: The triggering time of the filter is after the container and before the servlet, so the entry parameter of the filter doFilter (ServletRequest request, ServletResponse response, FilterChain chain) is ServletRequest, not HttpServletRequest, because the filter is before the HttpServlet. The following figure can give you a more intuitive understanding of the execution timing of Filter and Interceptor:
Only requests passed by DispatcherServlet will be followed by the interceptor chain. Custom Servlet requests will not be intercepted. For example, our customized Servlet address http://localhost:9090/testServlet will not be intercepted by the interceptor. But no matter which Servlet it belongs to, the filter will be executed as long as it complies with the filter's filter rules.
According to the above analysis, understanding the principle will be simple, even ASP.NET filters will be the same.
Problem: Achieve more flexible security authentication
Under the Java Web, through the custom filter Filter or the interceptor Interceptor, safe authentication of specific matching APIs can be achieved, such as matching all APIs, matching one or several APIs, etc., but sometimes this matching pattern is relatively not friendly to developers.
We can refer to Spring Security to achieve powerful functions through annotation + SpEL.
For example, in ASP.NET, we often use the Authorized feature, which can be added to classes or applied to methods, and can control security authentication more dynamically and flexibly.
We did not choose Spring Security, so we can implement flexible security certification similar to Authorized. The main implementation technology is the AOP we are familiar with.
The basic knowledge of achieving more flexible interception through AOP method will not be mentioned in this article. More topics about AOP will be shared in the next article.
Summarize
The above is what the editor introduced to you. Spring Boot uses filters and interceptors to achieve simple and secure authentication of REST interfaces. I hope it will be helpful to everyone. If you have any questions, please leave me a message and the editor will reply to everyone in time. Thank you very much for your support to Wulin.com website!