preface

In the process of common project development, there will be login interception, permission verification, parameter processing, anti-repeat submission and other problems. The interceptor can help us deal with these problems in a unified way.

1. Implementation method

1.1 Custom interceptors

A custom interceptor, the implementation class of an interceptor, can be customised in two ways:

  1. Define a class that implementsorg.springframework.web.servlet.HandlerInterceptorInterface.
  2. Define a class that inherits from a class that already implements the HandlerInterceptor interface, for exampleorg.springframework.web.servlet.handler.HandlerInterceptorAdapterAn abstract class.

1.2 Adding the Interceptor Interceptor to the WebMvcConfigurer configurator

Customize the configurator and then implement the WebMvcConfigurer configurator.

Normally inherited org. Springframework. Web. Servlet. Config. The annotation. WebMvcConfigurerAdapter class, The WebMvcConfigurerAdapter class is obsolete after SrpingBoot 2.0. There are 2 alternatives:

  1. Direct implementationorg.springframework.web.servlet.config.annotation.WebMvcConfigurerInterface. (recommended)
  2. inheritanceorg.springframework.web.servlet.config.annotation.WebMvcConfigurationSupportClass. But inherit WebMvcConfigurationSupport may make SpringBoot automatic configuration of MVC. But most current separation before and after the project is the end, and there is no demand for static resources have automatic configuration, so the inheritance WebMvcConfigurationSupport also have not cannot.

HandlerInterceptor

  1. preHandle: preprocessing, which is called before the service processor processes the request, and can be used for login interception, coding processing, security control, permission verification and other processing;
default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
	return true;
}
Copy the code
  1. postHandle: post-processing, which is called after the business processor has completed the execution of the processing request and before the view is generated. That is, the Service is called and ModelAndView is returned, but the page is not rendered. You can modify the ModelAndView, which is rarely used.
default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
			@Nullable ModelAndView modelAndView) throws Exception {}Copy the code
  1. afterCompletion: returns processing, which is called after the DispatcherServlet has fully processed the request and can be used to clean up resources, etc. The page has been rendered.
default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
			@Nullable Exception ex) throws Exception {}Copy the code

Third, Interceptor implementation

3.1 implementation HandlerInterceptor

This interceptor demonstrates interception validation of user permissions in the form of annotations.

package com.nobody.interceptor;

import com.nobody.annotation.UserAuthenticate;
import com.nobody.context.UserContext;
import com.nobody.context.UserContextManager;
import com.nobody.exception.RestAPIError;
import com.nobody.exception.RestException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/ * * *@Description
 * @Author Mr.nobody
 * @Date 2020/10/25
 * @Version1.0 * /
@Slf4j
@Component
public class UserPermissionInterceptor implements HandlerInterceptor {

    private UserContextManager userContextManager;

    @Autowired
    public void setContextManager(UserContextManager userContextManager) {
        this.userContextManager = userContextManager;
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {

        log.info(">>> UserPermissionInterceptor preHandle -- ");

        if (handler instanceof HandlerMethod) {
            HandlerMethod handlerMethod = (HandlerMethod) handler;

            // Get user permission validation annotations (first method, if not from class)
            UserAuthenticate userAuthenticate =
                    handlerMethod.getMethod().getAnnotation(UserAuthenticate.class);
            if (null == userAuthenticate) {
                userAuthenticate = handlerMethod.getMethod().getDeclaringClass()
                        .getAnnotation(UserAuthenticate.class);
            }
            if(userAuthenticate ! =null && userAuthenticate.permission()) {
                // Get user information
                UserContext userContext = userContextManager.getUserContext(request);
                // Check permissions
                if(userAuthenticate.type() ! = userContext.getType()) {// Return false if no exception is thrown
                    throw newRestException(RestAPIError.AUTH_ERROR); }}}return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
        log.info(">>> UserPermissionInterceptor postHandle -- ");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        log.info(">>> UserPermissionInterceptor afterCompletion -- "); }}Copy the code

3.2 inheritance HandlerInterceptorAdapter

package com.nobody.interceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.stereotype.Component;

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

/ * * *@Description
 * @Author Mr.nobody
 * @Date 2020/10/25
 * @Version1.0 * /
@Slf4j
@Component
public class UserPermissionInterceptorAdapter extends HandlerInterceptorAdapter {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        log.info(">>> UserPermissionInterceptorAdapter preHandle -- ");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
        log.info(">>> UserPermissionInterceptorAdapter postHandle -- ");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        log.info(">>> UserPermissionInterceptorAdapter afterCompletion -- "); }}Copy the code

Four, the configuration (WebMvcConfigurer) implementation

4.1 Implementing WebMvcConfigurer (Recommended)

package com.nobody.config;

import com.nobody.context.UserContextResolver;
import com.nobody.interceptor.UserPermissionInterceptor;
import com.nobody.interceptor.UserPermissionInterceptorAdapter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.List;

/ * * *@Description
 * @Author Mr.nobody
 * @Date 2020/10/25
 * @Version1.0 * /
@Configuration
public class WebAppConfigurer implements WebMvcConfigurer {

    private UserPermissionInterceptor userPermissionInterceptor;

    private UserPermissionInterceptorAdapter userPermissionInterceptorAdapter;

    private UserContextResolver userContextResolver;

    @Autowired
    public void setUserPermissionInterceptor(UserPermissionInterceptor userPermissionInterceptor) {
        this.userPermissionInterceptor = userPermissionInterceptor;
    }

    @Autowired
    public void setUserPermissionInterceptorAdapter( UserPermissionInterceptorAdapter userPermissionInterceptorAdapter) {
        this.userPermissionInterceptorAdapter = userPermissionInterceptorAdapter;
    }

    @Autowired
    public void setUserContextResolver(UserContextResolver userContextResolver) {
        this.userContextResolver = userContextResolver;
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // Multiple interceptors can be added, usually only one
        // addPathPatterns("/**") means all requests are intercepted
        ExcludePathPatterns ("/base/index") rules out intercepting /base/index requests
        // Multiple interceptors can set the order. The smaller the value, the more preHandle is executed first, and postHandle and afterCompletion are executed later
        // The default value of order is 0. If only one interceptor is added, the value of setting order can not be displayed
        registry.addInterceptor(userPermissionInterceptor).addPathPatterns("/ * *")
                .excludePathPatterns("/base/index").order(0);
        // registry.addInterceptor(userPermissionInterceptorAdapter).addPathPatterns("/**")
        // .excludePathPatterns("/base/index").order(1);
    }

    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) { resolvers.add(userContextResolver); }}Copy the code

4.2 inheritance WebMvcConfigurationSupport

package com.nobody.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;

import com.nobody.interceptor.UserPermissionInterceptor;
import com.nobody.interceptor.UserPermissionInterceptorAdapter;

/ * * *@Description
 * @Author Mr.nobody
 * @Date 2020/10/25
 * @Version1.0 * /
@Configuration
public class WebAppConfigurerSupport extends WebMvcConfigurationSupport {

    @Autowired
    private UserPermissionInterceptor userPermissionInterceptor;

    // @Autowired
    // private UserPermissionInterceptorAdapter userPermissionInterceptorAdapter;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // Multiple interceptors can be added, usually only one
        // addPathPatterns("/**") means all requests are intercepted
        ExcludePathPatterns ("/base/index") rules out intercepting /base/index requests
        registry.addInterceptor(userPermissionInterceptor).addPathPatterns("/ * *")
                .excludePathPatterns("/base/index");
        // registry.addInterceptor(userPermissionInterceptorAdapter).addPathPatterns("/**")
        // .excludePathPatterns("/base/index");}}Copy the code

V. Other main auxiliary classes

5.1 User context classes

package com.nobody.context;

import com.nobody.enums.AuthenticationTypeEnum;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

/ * * *@DescriptionUser context@Author Mr.nobody
 * @Date 2020/10/25
 * @Version1.0 * /
@Getter
@Setter
@ToString
public class UserContext {
    // User name
    private String name;
    / / user ID
    private String userId;
    // User type
    private AuthenticationTypeEnum type;
}
Copy the code

5.2 Verifying access Permission Annotations

package com.nobody.annotation;

import com.nobody.enums.AuthenticationTypeEnum;

import java.lang.annotation.*;

/ * * *@DescriptionVerify access annotation *@Author Mr.nobody
 * @Date 2020/10/25
 * @Version1.0 * /
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface UserAuthenticate {
    /** * Whether to verify access permissions By default, ** is not verified@return* /
    boolean permission(a) default false;

    /** * Validation type, default visitor **@return* /
    AuthenticationTypeEnum type(a) default AuthenticationTypeEnum.VISITOR;
}
Copy the code

5.3 User context operation classes

package com.nobody.context;

import com.nobody.enums.AuthenticationTypeEnum;
import com.nobody.exception.RestAPIError;
import com.nobody.exception.RestException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Objects;
import java.util.UUID;

/ * * *@DescriptionUser context action class *@Author Mr.nobody
 * @Date 2020/10/25
 * @Version1.0 * /
@Component
public class UserContextManager {

    private static final String COOKIE_KEY = "__userToken";

    // @Autowired
    // private RedisService redisService;

    /** * Get user context information **@param request
     * @return* /
    public UserContext getUserContext(HttpServletRequest request) {
        String userToken = getUserToken(request, COOKIE_KEY);
        if(! StringUtils.isEmpty(userToken)) {// Get user information from cache or third party
            // String userContextStr = redisService.getString(userToken);
            // if (! StringUtils.isEmpty(userContextStr)) {
            // return JSON.parseObject(userContextStr, UserContext.class);
            // }
            // For demonstration purposes, there is no integration of Redis, so simple new object
            UserContext userContext = new UserContext();
            userContext.setName("Mr.nobody");
            userContext.setUserId("0000001");
            userContext.setType(AuthenticationTypeEnum.ADMIN);
            return userContext;
        }
        throw new RestException(RestAPIError.AUTH_ERROR);
    }

    public String getUserToken(HttpServletRequest request, String cookieKey) {
        Cookie[] cookies = request.getCookies();
        if (null! = cookies) {for (Cookie cookie : cookies) {
                if (Objects.equals(cookie.getName(), cookieKey)) {
                    returncookie.getValue(); }}}return null;
    }

    /** * Saves user context information **@param response
     * @param userContextStr
     */
    public void saveUserContext(HttpServletResponse response, String userContextStr) {
        // The user token is generated based on the user's own services. In this case, the UUID is simply used
        String userToken = UUID.randomUUID().toString();
        / / set the cookie
        Cookie cookie = new Cookie(COOKIE_KEY, userToken);
        cookie.setPath("/");
        response.addCookie(cookie);
        / / redis cache
        // redisService.setString(userToken, userContextStr, 3600);}}Copy the code

5.4 Method parameter parser class

package com.nobody.context;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.MethodParameter;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;

import javax.servlet.http.HttpServletRequest;

/ * * *@DescriptionIntercepting and injecting user information * for interfaces with UserContext parameters@Author Mr.nobody
 * @Date 2020/10/25
 * @Version1.0 * /
@Component
@Slf4j
public class UserContextResolver implements HandlerMethodArgumentResolver {

    @Autowired
    private UserContextManager userContextManager;

    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) {
        log.info(">>> resolveArgument -- begin...");
        HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
        // Get user information from the cache and assign it to the interface parameter
        return userContextManager.getUserContext(request);
    }

    /** * Intercepts only the UserContext argument **@param methodParameter
     * @return* /
    @Override
    public boolean supportsParameter(MethodParameter methodParameter) {
        if (methodParameter.getParameterType().equals(UserContext.class)) {
            return true;
        }
        return false; }}Copy the code

Test and verification

package com.nobody.controller;

import com.alibaba.fastjson.JSON;
import com.nobody.annotation.UserAuthenticate;
import com.nobody.context.UserContext;
import com.nobody.context.UserContextManager;
import com.nobody.enums.AuthenticationTypeEnum;
import com.nobody.pojo.model.GeneralResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletResponse;

/ * * *@Description
 * @Author Mr.nobody
 * @Date 2020/10/25
 * @Version1.0 * /
@RestController
@RequestMapping("user")
public class UserController {

    @Autowired
    private UserContextManager userContextManager;

    @GetMapping("login")
    public GeneralResult<UserContext> doLogin(HttpServletResponse response) {
        UserContext userContext = new UserContext();
        userContext.setUserId("0000001");
        userContext.setName("Mr.nobody");
        userContext.setType(AuthenticationTypeEnum.ADMIN);
        userContextManager.saveUserContext(response, JSON.toJSONString(userContext));
        return GeneralResult.genSuccessResult(userContext);
    }

    @GetMapping("personal")
    @UserAuthenticate(permission = true, type = AuthenticationTypeEnum.ADMIN)
    public GeneralResult<UserContext> getPersonInfo(UserContext userContext) {
        returnGeneralResult.genSuccessResult(userContext); }}Copy the code

The personal interface will be called in the browser first after the service is started.

Console output:

After starting the service, login to the login interface and then to the personal interface in the browser. If the authentication succeeds, the user information is returned correctly:

Github Project

The project is available at Github, github.com/LucioChn/sp…