登录认证(6):拦截器:Interceptor

292 阅读7分钟

拦截器:Interceptor

上文提到,可以使用Servlet中的组件——Filter过滤器进行统一拦截,实现登录认证的功能。但在Spring框架中,也提供了一个类似于Filter过滤器的东西,那就是——拦截器Interceptor,拦截器是一种动态拦截方法调用的机制,其逻辑和Filter过滤器类似。

其主要作用也是拦截请求,在请求到服务端之前进行拦截,根据需求执行预先设定的代码。在拦截器中,一般也是做一些通用性操作,比如说实现登录校验,其实现逻辑和Filter过滤器类似,此处不做赘述 (详情请看:登录认证(5):过滤器:Filter

拦截器快速入门

拦截器的快速入门也分两步:定义拦截器和注册配置拦截器,这也是和Filter过滤器相似的地方。

自定义拦截器

自定义拦截器需要定义一个类,并实现HandlerInterceptor接口,并重写其中所有方法:

@Slf4j
@Component
public class DemoInterceptor implements HandlerInterceptor {

    // 访问目标资源之前执行,返回true:放行 false:不放行
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        log.info("preHandle...");
        return true; // 放行
    }

    // 目标资源方法执行之后执行
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
                           ModelAndView modelAndView) throws Exception {
        log.info("postHandle...");
    }

    // 视图渲染完成之后执行,是最后执行的
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
                                Exception ex) throws Exception {
        log.info("afterCompletion...");
    }
}

preHandle方法

preHandle方法,在请求目标资源方法执行之前执行,其boolean类型的返回值代表了是否放行,如果返回true,则放行;返回false,则不放行。

postHandle方法

postHandle方法,在请求目标资源方法执行之后执行,可以根据需求,在请求返回客户端之前,进行额外的处理。

afterCompletion方法

afterCompletion方法,在视图渲染完毕之后执行,在早期开发中,没有分前后端,前端的视图渲染也需要后端服务器完成,afterCompletion方法会在视图渲染完成之后执行。但是现在前端的视图渲染是由单独的前端服务器完成,所以说这个方法了解即可。

自定义拦截器之后,现在该拦截器还是不能工作的,还需要注册配置拦截器。首先需要创建一个配置类,配置类需要实现WebMvcConfigurer接口,并重写addInterceptors方法,在addInterceptors方法中,需要注册自定拦截器对象,并且设置拦截器拦截的请求路径。

/**
 * 配置类,配置Interceptor拦截器
 */
@Configuration
public class WebConfig implements WebMvcConfigurer {

    // 定义需要配置的拦截器对象
    private final DemoInterceptor demoInterceptor;
    
    @Autowired
    public WebConfig(DemoInterceptor demoInterceptor) {
        this.demoInterceptor = demoInterceptor;
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 注册自定义拦截器对象
       registry.addInterceptor(demoInterceptor).addPathPatterns("/**"); // 设置拦截路径
    }
}

由于我们给自定义的DemoInterceptor拦截器对象添加了@Compnent注解,使其加入了IOC容器,所以说我们可以在配置类中直接使用@AutoWired注解进行依赖注入。在addInterceptors方法中,通过InterceptorRegistry类addInterceptor方法,注册自定义拦截器对象,并在addPathPatterns方法中设置拦截路径,这样,拦截器的配置就完成了,拦截器就可以正常工作了。此时发起请求测试(此时建议将前面配置的过滤器关闭 ):

image.png

image.png

我们看到,因为在preHandle方法中直接返回了true,所以说所有的请求都会直接放行,所以说就可以成功访问到服务器资源,并且这三个方法的运行顺序,也和我们分析的一致。

登录校验Interceptor

讲解完Interceptor拦截器的快速入门之后,我们需要仿造Filter过滤器的逻辑,使用Interceptor拦截器,完成登录校验的功能。其逻辑和实现步骤在介绍Filter过滤器的时候已经讲解过,二者的逻辑是完全一致的,所以说此处不做赘述,只需要将原来的技术方案从过滤器换成拦截器即可:

/**
 * 登录拦截器 
 */
@Slf4j
@Component
public class LoginInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws  Exception {
        // 获取URL,如果是登录,那么放行
        String url = request.getRequestURL().toString();
        if (url.contains("/login")) {
            log.info("登录请求,放行...");
            return true;
        }

        // 获取token
        String token = request.getHeader("token");

        // 如果token为null,表示未登录,返回401
        if (token == null || token.isEmpty()) {
            log.info("未登录,不放行");
            response.setStatus(401);
            return false;
        }

        // 解析token,如果无法解析,说明token错误,登录失败,返回401
        try {
            Claims claims = JWTUtils.parseJWT(token);
            ThreadUtils.setCurrentId((Integer)claims.get("id"));
        } catch (Exception e) {
            e.printStackTrace();
            log.info("token解析失败,登录失败,不放行");
            response.setStatus(401);
            return false;
        }

        // 如果token解析成功,说明登录成功,可以放行
        log.info("token成功解析,登录成功,放行");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
                           @Nullable ModelAndView modelAndView) throws Exception {
        // 清空当前线程的存储空间
        ThreadUtils.remove();
    }
}

配置拦截器:

/**
 * 配置类,配置Interceptor拦截器
 */
@Configuration
public class WebConfig implements WebMvcConfigurer {

    // 定义需要配置的拦截器对象
    private final LoginInterceptor loginInterceptor;

    @Autowired
    public WebConfig(LoginInterceptor loginInterceptor) {
        this.loginInterceptor = loginInterceptor;
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 注册自定义拦截器对象
       registry.addInterceptor(loginInterceptor).addPathPatterns("/**"); // 设置拦截路径
    }
}

配置完毕后,启动服务,发起请求进行测试:

image.png

此时用户没有登录,自然没有JWT令牌,发起请求被拦截器拦截,不会放行。

image.png

image.png

此时发起登录请求,根据我们的逻辑:登录请求直接放行,所以说请求成功访问服务器资源,获得了JWT令牌并返回,后续的请求都要携带JWT令牌进行访问。

image.png

携带正确的JWT令牌发起请求,成功访问到服务器资源

image.png

篡改JWT令牌,解析失败,请求被拦截,根据以上测试结果,说明我们配置的登录拦截器成功运行。

Interceptor详解

拦截路径

注册配置拦截器的时候,我们是通过addPathPattern("需要拦截的路径")完成拦截路径配置的,但是Interceptor拦截器十分的智能,不但可以指定需要拦截的资源,还可以指定不拦截的资源,需要使用excludePathPattern("不拦截的路径")方法,指定不拦截的资源:

@Override
public void addInterceptors(InterceptorRegistry registry) {
    // 注册自定义拦截器对象
   registry.addInterceptor(loginInterceptor).addPathPatterns("/**").excludePathPatterns("/login"); // 设置拦截路径
}

这样,登录请求就不会再拦截了,就不再需要在拦截方法中进行判断了。

拦截器除了可以使用/**拦截所有资源外,还可以根据需求进行不同的拦截路径设置,其中常见的有:/*是拦截一级路径,可以拦截/depts/emps等类似的路径,但是只能拦截到一级,不能匹配/depts/1这样的路径;/**是拦截任意级路径,所有路径都会被拦截;/depts/*是拦截/depts下的一级路径,可以匹配/depts/1这样的路径,但是无法匹配到/depts/1/2/depts这样的路径;/depts/**是拦截/depts下的任意级路径,如/deptsdepts/1/2depts/1这样的路径,但是不能匹配到/emps这样的路径。

执行流程

拦截器大致执行流程如图所示:

image.png

如图所示,当发起请求时,假如服务器同时存在Filter过滤器Interceptor拦截器,会先执行Filter过滤器,这其实很好解释,因为过滤器是原生的Servlet中的组件,而拦截器是Spring框架中提供的技术,换句话说:Interceptor拦截器只会拦截Spring环境中的资源,而过滤器会拦截所有的资源。

Tomcat服务器并不识别Spring框架中的Controller程序,只识别Servlet程序,所以说Spring框架必须依赖一个核心的Servlet——DispatcherServlet(前端控制器),前端发起的所有请求,都会先经过DispatcherServlet,再由其将请求转给Controller,在转给Controller之前,如果定义了拦截器,那么就会被拦截器拦截,执行preHandler方法,方法的返回值是一个boolean类型,真假代表了是否拦截器是否放行。Controller中的方法执行完毕之后,再会回到拦截器中执行postHandleafterCompletion方法,然后会回到Filter过滤器中,执行放行后逻辑,最后才给前端响应数据。

总结

原生Servlet提供的Filter过滤器Spring框架提供的Interceptor拦截器都可以在请求到达web服务器之前,对请求进行统一拦截,二者的功能相似,实现略有差别,需要根据不同的需求进行使用。并且Interceptor拦截器只会拦截Spring环境中的资源,而Filter过滤器的拦截范围为所有资源,范围更大。并且由于Spring系列框架越来越广泛,Interceptor拦截器的使用高于Filter过滤器