前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Spring Security技术栈开发企业级认证与授权(五)使用Filter、Interceptor和AOP拦截REST服务

Spring Security技术栈开发企业级认证与授权(五)使用Filter、Interceptor和AOP拦截REST服务

作者头像
itlemon
发布2020-04-03 17:09:22
7940
发布2020-04-03 17:09:22
举报
文章被收录于专栏:深入理解Java深入理解Java

一般情况,在访问RESTful风格的API之前,可以对访问行为进行拦截,并做一些逻辑处理,本文主要介绍三种拦截方式,分别是:过滤器Filter、拦截器Interceptor以及面向切面的拦截方式AOP

一、使用过滤器Filter进行拦截

使用过滤器进行拦截主要有两种方式,第一种是将自定义的拦截器标注为SpringBean,在Spring Boot应用就可以对RESTful风格的API进行拦截。第二种方式往往应用在继承第三方过滤器,这时候就需要将第三方拦截器使用FilterRegistrationBean对象进行注册即可。接下来详细介绍两种方式。

  • 将拦截器标注为SpringBean
代码语言:javascript
复制
package com.lemon.security.web.filter;

import org.springframework.stereotype.Component;

import javax.servlet.*;
import java.io.IOException;

/**
 * @author lemon
 * @date 2018/4/1 下午10:19
 */
@Component
public class TimeFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("time filter init.");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        System.out.println("time filter start.");
        long startTime = System.currentTimeMillis();
        chain.doFilter(request, response);
        System.out.println("time filter 耗时: " + (System.currentTimeMillis() - startTime));
        System.out.println("time filter finish.");
    }

    @Override
    public void destroy() {
        System.out.println("time filter destroy.");
    }
}

启动Spring Boot应用的时候,上面的拦截器就会起作用,当访问每一个服务的时候,都会进入这个拦截器中。初始化方法init和销毁方法destroy只会调用一次,分别是应用启动时候调用init方法,应用关闭时候调用destroy方法。而doFilter方法则在每次都会调用。

  • 将拦截器作为第三方拦截器进行注册

使用的类还是上面的同一个类,只不过这次不需要@Component注解,这时候我们需要自己写一个配置类,将过滤器注册到Spring容器中。推荐使用这种方式,因为这种方式我们可以自己设置需要拦截的API,否则第一种方式是拦截所有的API

代码语言:javascript
复制
package com.lemon.security.web.config;

import com.lemon.security.web.filter.TimeFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.ArrayList;
import java.util.List;

/**
 * @author lemon
 * @date 2018/4/1 下午10:34
 */
@Configuration
public class WebConfig {

    @Bean
    public FilterRegistrationBean timeFilter() {
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
        TimeFilter timeFilter = new TimeFilter();
        filterRegistrationBean.setFilter(timeFilter);
        List<String> urls = new ArrayList<>();
        urls.add("/*");
        filterRegistrationBean.setUrlPatterns(urls);
        return filterRegistrationBean;
    }
}

这里我设置的仍然是拦截所有的API,可以设置为自定义的方式对API进行拦截。

二、使用拦截器Interceptor进行拦截

这里需要定义一个拦截器类,并实现HandlerInterceptor接口,这个接口有三个方法需要实现,分别是:

代码语言:javascript
复制
boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception;
void postHandle(
			HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
			throws Exception;
void afterCompletion(
			HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
			throws Exception;

下面对三个方法进行一一解释:

  • preHandle方法的第三个参数是具体的API处理方法的Method对象,我们可以将其强转为HandlerMethod,然后就可以获取该Method的一些属性,比如方法名,方法所在类的类名等信息。preHandle是当访问API之前,都要进入这个方法,由这个方法进行一些逻辑处理,如果处理完结果返回true,那么将继续进入到具体的API中,否则将就地结束访问,逻辑不会进入API方法中。
  • postHandle方法是在API方法访问完成之后立即进入的方法,可以处理一些逻辑,比如将API中的数据封装到ModelAndView中,如果前面的preHandle方法返回false,将不会执行该方法,如果API方法发生了异常,也将不会调用此方法。
  • afterCompletion方法的调用只要preHandle方法通过之后就会调用它,不论API方法是否出现了异常。如果出现了异常,将被封装到Exception对象中。

下面,写一个自定义的类来实现上述接口:

代码语言:javascript
复制
package com.lemon.security.web.interceptor;

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;

/**
 * @author lemon
 * @date 2018/4/1 下午10:39
 */
@Component
public class TimeInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("preHandler");
        System.out.println(((HandlerMethod) handler).getBean().getClass().getName());
        System.out.println(((HandlerMethod) handler).getMethod().getName());
        request.setAttribute("startTime", System.currentTimeMillis());
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("postHandler");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("afterCompletion");
        System.out.println("TimeInterceptor耗时:" + (System.currentTimeMillis() - (Long) request.getAttribute("startTime")));
    }
}

这里需要将其标注为SpringBean,但是仅仅标注为Bean还是不够的,需要在配置类中进行配置。代码如下:

代码语言:javascript
复制
package com.lemon.security.web.config;

import com.lemon.security.web.interceptor.TimeInterceptor;
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.WebMvcConfigurerAdapter;

/**
 * @author lemon
 * @date 2018/4/1 下午10:34
 */
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {

    @Autowired
    private TimeInterceptor timeInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(timeInterceptor);
    }
}

这个配置类需要继承WebMvcConfigurerAdapter,并重写添加拦截器的方法addInterceptors,将自定义拦截器添加到应用中。这时候拦截器就生效了。

三、使用AOP进行拦截

其实是有拦截器InterceptorAPI进行拦截的时候是有缺陷的,因为无法获取前端访问API的时候所携带的参数的,为什么会这么说?从Spring MVCDispatcherServlet的源代码中可以发现,找到doDispatch方法,也就是请求分发的方法,有一段代码如下:

这里写图片描述
这里写图片描述

如果我们自定的InterceptorpreHandler方法返回的是false,分发任务就会截止,不再继续执行下面的代码,而下面的一行代码正是将前端携带的参数进行映射的逻辑,也就是说,preHandler方法不会接触到前端携带来的参数,也就是说拦截器无法处理参数。所以这里引进AOP进行拦截。 AOP的核心概念解释: 描述AOP常用的一些术语有通知(Adivce)、切点(Pointcut)、连接点(Join point)、切面(Aspect)、引入(Introduction)、织入(Weaving

  • 通知(Advice

通知分为五中类型: Before:在方法被调用之前调用 After:在方法完成后调用通知,无论方法是否执行成功 After-returning:在方法成功执行之后调用通知 After-throwing:在方法抛出异常后调用通知 Around:通知了好、包含了被通知的方法,在被通知的方法调用之前后调用之后执行自定义的行为

  • 连接点(Join point

连接点是一个应用执行过程中能够插入一个切面的点。比如:方法调用、方法执行、字段设置/获取、异常处理执行、类初始化、甚至是for循环中的某个点。理论上, 程序执行过程中的任何时点都可以作为作为织入点, 而所有这些执行时点都是Joint point,但 Spring AOP 目前仅支持方法执行 (method execution)。

  • 切点(Pointcut

通知(advice)定义了切面何时,那么切点就是定义切面“何处” 描述某一类 Joint points, 比如定义了很多 Joint point, 对于 Spring AOP 来说就是匹配哪些方法的执行。

  • 切面(Aspect

切面是切点和通知的结合。通知和切点共同定义了关于切面的全部内容,它是什么时候,在何时和何处完成功能。

  • 引入(Introduction

引用允许我们向现有的类添加新的方法或者属性

  • 织入(Weaving

组装方面来创建一个被通知对象。这可以在编译时完成(例如使用AspectJ编译器),也可以在运行时完成。Spring和其他纯Java AOP框架一样,在运行时完成织入。

上面的概念有点生涩难懂,总结一个核心内容:切面 = 切点 + 通知。 现在通过代码来编写一个切面:

代码语言:javascript
复制
package com.lemon.security.web.aspect;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

/**
 * @author lemon
 * @date 2018/4/2 上午10:40
 */
@Aspect
@Component
public class TimeAspect {

    @Around("execution(* com.lemon.security.web.controller.UserController.*(..))")
    public Object handleTime(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("time aspect is start.");
        for (Object object : proceedingJoinPoint.getArgs()) {
            System.out.println(object);
        }
        long startTime = System.currentTimeMillis();
        Object obj = proceedingJoinPoint.proceed();
        System.out.println("time aspect 耗时:" + (System.currentTimeMillis() - startTime));
        System.out.println("time aspect finish.");
        return obj;
    }
}

@Around定义了环绕通知,也就是定义了何时使用切面,表达式"execution(* com.lemon.security.web.controller.UserController.*(..))"定义了再哪里使用。ProceedingJoinPoint对象的proceed()方法表示执行被拦截的方法,它有一个Object类型的返回值,是原有方法的返回值,后期使用的时候往往需要强转。关于切点的表达式,可以访问Spring官方文档。 对于上面三种拦截方式,他们的执行有一个基本的顺序,进入的顺序是Filter-->Interceptor-->Aspect-->Controller-->Aspect-->Interceptor-->Filter(不考虑异常的发生)。如下图所示:

这里写图片描述
这里写图片描述
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2018/04/02 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、使用过滤器Filter进行拦截
  • 二、使用拦截器Interceptor进行拦截
  • 三、使用AOP进行拦截
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档