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