springboot uses filters and interceptors to design login-state interfaces

Scenario: You need to write a login-enabled interface to inject the token-to-object request into the Controller's method, similar to @RequestBody, where token is inside the header where the request exists.

Design idea: use a filter to get the token inside the request, get the token into a user object, then save the object into the attribute s of the request, and define a parameter parser to parse the user object into the Controller.

1. Define a comment first, similar to @RequestBody

import java.lang.annotation.*;

@Target({ ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface APPSSOAcctInfo {
}

2. Look at the interfaces defined in Controller

@GetMapping(value="/getData")
public String getData(@APPSSOAcctInfo AccountDTO accountDTO){
    ......//Business Code
}

3. Define Filter

@Component
public class APPSSOFilter extends OncePerRequestFilter {

    //Interface that does not need to resolve token
    private static final List<String> excludeURIs = new ArrayList<>();

    static {
        excludeURIs.add("/notUser/*");
    }

    @Override
    public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws IOException {
        try {
            String requestUri = request.getRequestURI();
            String contextPath = request.getContextPath();
            String url = requestUri.substring(contextPath.length());

            if (judge excludeURIs Is this request included url,If included then you do not need to get it from the parameter token Transition user object) {
                filterChain.doFilter(request, response);
                return;
            }

            AccountDTO accountDTO = adopt token Conversion AccountDTO;
            //Put the resulting AccountDTO in the attribute s of the request because we'll fetch it later
            request.setAttribute("APPSSOAcctInfo", accountDTO);
            filterChain.doFilter(request, response);//Release
        } catch (Exception e) {
            errorReturn(request, response);
        }
    }

    //In case of failure, this is free to play
    private void errorReturn(HttpServletRequest request,HttpServletResponse response) throws IOException {
        response.setContentType("application/json;charset=UTF-8");
        response.setStatus(200);
        PrintWriter writer = response.getWriter();
        writer.write(JSON.toJSONString("fail"));
        writer.close();
    }
}

4. Add the filter defined above to the call chain of springboot, where I use the FilterRegistrationBean

@Configuration
public class DemoConfiguration {
	
	@Autowired
    private APPSSOFilter appssoFilter;//Inject the Filter defined above
    @Bean
    public FilterRegistrationBean demoFilterRegistration() {
        FilterRegistrationBean registration = new FilterRegistrationBean();
        registration.setFilter(appssoFilter);//Add Filter
        registration.setOrder(1);//set priority
        registration.addUrlPatterns("/*"); //Set Path to Intercept
        return registration;
    }

    //If you need to inject more than one Filter to handle different businesses
    /*
    @Autowired
	private TestFilter2 testFilter2; This Filter is defined the same way as above
	@Bean
	public FilterRegistrationBean nameListFilterRegistration() {
	    FilterRegistrationBean registration = new FilterRegistrationBean();
	    registration.setFilter(testFilter2);
	    registration.setOrder(2);
	    registration.addUrlPatterns("/*");
	    return registration;
	}
	*/
}

5. Define a parameter parser to do the same thing as @RequestBody, assemble the parameter parsing from the request into a DTO and put it in the Controller

public class APPSSOAcctInfoMethodArgumentResolver implements HandlerMethodArgumentResolver {

    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        //Determine if Controller's method contains the APPSSOAcctInfo annotation
        return parameter.hasParameterAnnotation(APPSSOAcctInfo.class);
    }

    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
                                  NativeWebRequest webRequest, WebDataBinderFactory binderFactory) {
        //Filter that step we've saved the DTO in the request attribute s, now we just need to take it out.
        HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
        return request.getAttribute("APPSSOAcctInfo");
    }
}

HandlerMethodArgumentResolver knowledges:
The supportsParameter method returns true, indicating that the resolveArgument method will be called.

6. Put the parameter parser defined above into MVC so that when it starts, it will load the parser.

@Configuration
public class WebAppMvcConfig extends WebMvcConfigurationSupport {

    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
        //Add our own parameter parser
        argumentResolvers.add(new APPSSOAcctInfoMethodArgumentResolver());
    }
}

WebMvcConfiguration Support knowledges:
This class is very extensible, so we need to customize everything in MVC and implement corresponding methods in this class.

The problem described at the beginning of this article has been implemented.

Re-engineering: Later, because a colleague wrote an account center with the spring starter, directly introduced the jar package, defined Filter in the jar package to parse the user token, and put the user object in the ThreadLocal, simply in the code through ThreadLocal.get() So we don't need to parse token, we need to remove the custom filter, but don't want to change too much code, or we want to get the user object in Conterller by annotation @APPSSOAcctInfo. Here's a small change to the original code

Innovation ideas: Because there are many filters in jar, this time we don't need FIlter to do it. Filter has more execution order which is not well controlled. For example, after jar's FIlter parses token after my filter, it can't set login state to request. So this time I use interceptor to implement attribute s of requests to set user object, so I can ensure that intercept be executed at the same time.Must have passed token to AccountDTO

1. Define Interceptors

public class AuthenticationInterceptor implements HandlerInterceptor{
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        accountDTO = ThreadLocal.get();//This is pseudo code, get the AccountDTO that jar set into the Filter through ThreadLocal
		request.setAttribute("APPSSOAcctInfo",accountDTO);
        return true;
    }
}

Don't know how to set objects in ThreadLocal to Baidu

2. Add interceptor to MVC

@Configuration
public class WebAppMvcConfig extends WebMvcConfigurationSupport {

    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
        //Add our own parameter parser
        argumentResolvers.add(new APPSSOAcctInfoMethodArgumentResolver());
    }
    
    //Just append the next paragraph
    @Override
    public void addInterceptors(InterceptorRegistry interceptorRegistry) {
        interceptorRegistry.addInterceptor(new AuthenticationInterceptor())//Add Interceptor
                .addPathPatterns("/**")  //Set up the road that needs to be intercepted
                .excludePathPatterns("/health");  //Set Ignored Path
    }
}

Note: Don't forget to comment out the demoFilterRegistration method that registered Filter earlier

Up to this point, the project has been renovated. Here are a few of the classes discussed below

Keywords: Java Spring mvc

Added by gwh on Sun, 12 Sep 2021 20:07:15 +0300