Spring Boot 优雅的配置拦截器


placeholder image
admin 发布于:2022-05-09 21:47:30
阅读:loading

拦截器一直都是Spring项目中重要的组成部分,很多场景我们都需要对请求URL地址进行过滤和拦截,而在于Spring Boot中增加一个拦截器也是比较方便的,但是经过实践时发现全是以编程式的将一些需要过滤的URL和排除的URL路径以代码的形式进行硬编码,作为一枚自认为非常专业的选手来说,这种写法用于URL的权限过滤是必须要去解决的,必须要实现一个可在xml中配置的拦截器参数配置,即所谓优雅的配置拦截器的实现,所以先来看下一个简单的拦截器的具体实现,详见如下。

拦截器的实现

(1)增加拦截器的实现类,此处方法的拦截等实现均为输入一行warn日志,参考代码如下:

package cn.chendd.base.spring.interceptors;

import ...

/**
 * ApiVersionVaildator拦截器
 *
 * @author chendd
 * @date 2022/5/29 10:15
 */
@Slf4j
public class ApiVersionValidatorInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        log.warn("ApiVersionValidatorInterceptor.preHandle:{}" , request.getRequestURL());
        return HandlerInterceptor.super.preHandle(request, response, handler);
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        log.warn("ApiVersionValidatorInterceptor.postHandle:{}" , request.getRequestURL());
        HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        log.warn("ApiVersionValidatorInterceptor.afterCompletion:{}" , request.getRequestURL());
        HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
    }
}

(2)增加拦截器的使用配置,默认拦截/v1/**开头的路径,同时排除/v1/login和/v1/login/*的路径,参考代码如下:

package cn.chendd.base.spring.components;

import ...

import java.util.List;

/**
 * Webmvc配置
 *
 * @author chendd
 * @date 2019/10/17 11:03
 */
@Configuration
public class WebMvcRegistrationsConfig extends WebMvcConfigurationSupport {

    ///此处省略一大堆非拦截器示例的代码实现

    @Override
    protected void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new ApiVersionValidatorInterceptor())
                .addPathPatterns("/v1/**").excludePathPatterns("/v1/login" , "/v1/login/*");
        super.addInterceptors(registry);
    }

}

(3)编写Controller测试调用拦截器触发的路径方法,调用后会在匹配的路径下触发对应的方法拦截执行,代码参考如下:

package cn.chendd.modules.interceptor.controller;

import ...
/**
 * 拦截器Controller接口定义
 *
 * @author chendd
 * @date 2022/5/29 10:22
 */
@RestController
@ApiVersion("1")
@Api(value = "测试拦截器解析响应验证接口" , tags = {"拦截器解析响应验证"})
public class InterceptorController {

    @GetMapping(value = "/api" , produces = MediaType.APPLICATION_JSON_VALUE)
    @ApiOperation(value = "测试Api接口请求拦截器触发",notes = "测试响应结果集(<b>Api接口请求</b>)",
            consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
    public String apiInterceptor() {
        return "关注控制台Api Interceptor拦截器执行的warn日志";
    }

    @GetMapping(value = "/login" , produces = MediaType.APPLICATION_JSON_VALUE)
    @ApiOperation(value = "测试Login接口请求拦截器触发",notes = "测试响应结果集(<b>Login接口请求</b>)",
            consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
    public String loginInterceptor() {
        return "关注控制台Login Interceptor拦截器执行的warn日志";
    }

}

优雅的实现拦截器

本文所谓的优雅的实现拦截器最主要体现在实现拦截器时的时候对于参数路径的拦截和放行从硬编码变成从beans.xml中进行配置,同时将拦截器的实现结构进行统一包装,详细实现如下:

(1)服务器启动时加载下列逻辑,动态增加拦截器实现类,同时设置拦截器拦截和放行路径,参考代码如下:

@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
    //增加初始化自定义的拦截器
    this.addInterceptors();
}

/**
 * 动态增加拦截器方法实现
 */
@SuppressWarnings("unchecked")
private void addInterceptors() {
    try{
        String[] names = SpringBeanFactory.getApplicationContext().getBeanNamesForType(InterceptorConfiguration.class);
        ApiRequestMappingHandlerMapping handlerMapping = SpringBeanFactory.getBean(ApiRequestMappingHandlerMapping.class);
        for (String name : names) {
            InterceptorConfiguration config = SpringBeanFactory.getBean(name , InterceptorConfiguration.class);
            String beanClassName = config.getBeanClassName();
            List<String> includeMapping = config.getIncludeMapping();
            List<String> excludeMapping = config.getExcludeMapping();
            HandlerInterceptor interceptor = (HandlerInterceptor) ConstructorUtils.invokeConstructor(Class.forName(beanClassName) , config);
            InterceptorRegistry interceptorRegistry = new InterceptorRegistry();
            interceptorRegistry.addInterceptor(interceptor).addPathPatterns(includeMapping).excludePathPatterns(excludeMapping);
            List<Object> rrrList = (List<Object>) MethodUtils.invokeMethod(interceptorRegistry , true , "getInterceptors");
            handlerMapping.setInterceptors(rrrList.toArray());
        }
        MethodUtils.invokeMethod(handlerMapping , true , "initInterceptors");
    } catch (Exception e) {
        BaseLog.getLogger().error("WebMvcRegistrationsConfig addInterceptors is error" , e);
    }
}

(2)提供InterceptorConfiguration类作为拦截器参数对象可在spring beans.xml中进行重复定义,每个定义需要传递具体拦截器实现的类路径和拦截器真实拦截与放行的集合数据,由服务器启动时的mvc环境触发,参考代码与xml配置实现如下:

package cn.chendd.base.spring.components;

import ...

import java.util.List;

/**
 * 拦截器地址配置
 *
 * @author chendd
 * @date 2021/2/14 19:54
 */
@Getter
@Setter
public class InterceptorConfiguration {

    /**
     * 拦截器实现类地址
     */
    private String beanClassName;
    /**
     * 拦截器拦截的路径范围
     */

    private List<String> includeMapping = Lists.newArrayList();
    /**
     * 按请求类型放行
     */
    private List<String> excludeMappingTypes = Lists.newArrayList();
    /**
     * 拦截器放行地址
     */
    private List<String> excludeMapping = Lists.newArrayList();

}
<bean id="exampleInterceptor" class="cn.chendd.base.spring.components.InterceptorConfiguration">
    <property name="beanClassName" value="cn.chendd.base.spring.interceptors.ExampleValidatorInterceptor" />
    <property name="includeMapping">
        <!--包含过滤的路径-->
        <list>
            <value>/v2/example/**</value>
        </list>
    </property>
    <!--拦截器放行路劲-->
    <property name="excludeMapping">
        <list>
            <value>/v2/example/123</value>
        </list>
    </property>
    <!--按请求方式放行,与之匹配到的地址,仅拦截对应请求方式,否则直接放行-->
    <property name="excludeMappingTypes">
        <list>
            <value>/v2/example/cdd POST</value>
        </list>
    </property>
</bean>

(3)运行示例详见最底部的源码下载

image.png

示例说明与知识点

(A)提供多个拦截器的配置,分别是Example和Hello两个;

(B)假设拦截路径为/v2/example/**,对于路径/v2/example/123直接放行,对于路径/v2/example/abc拦截后放行,对于路径/v2/hello/cdd + POST拦截后进行逻辑校验后的放行;

(C)源代码中提供同样的/v2/hello/**拦截示例,细节与上述完全一致;

(D)beanClassName为真实拦截器实现类;includeMapping表示拦截器拦截路径,可配置多个路径,也支持通配符匹配;excludeMapping表示拦截器放行路径,可配置多个路径,也支持通配符匹配;excludeMappingTypes表示拦截器放行路径,支持路径和请求类型(GET/POST等),需要在拦截器中增加逻辑校验;

(4)关于统配路径的参考,直接分享当前博客源码中的内容,包含多种统配匹配格式的具体应用,具体参考如下:

<!-- 前端系统 -->
<bean id="loginInterceptor" class="cn.chendd.blog.base.spring.component.InterceptorConfiguration">
    <property name="beanClassName" value="cn.chendd.blog.base.spring.interceptor.LoginValidatorInterceptor"/>
    <!--包含过滤的路径-->
    <property name="includeMapping">
        <list>
            <value>/**/*.html</value>
        </list>
    </property>
    <property name="excludeMapping">
        <list>
            <!--<value>/</value>-->
            <value>/statics/**</value>
            <value>/</value>
            <value>/index.html</value>
            <!-- 用户登录 -->
            <value>/login.html</value>
            <!-- 用户退出 -->
            <value>/logout.html</value>
            <value>/third-login/*.html</value>
            <!-- 未登录页面 -->
            <value>/blog/frame/login.html</value>
            <!-- 查看文章明细页面,前面的articleId为controller中的@PathVariable变量名称,解析见AntPathStringMatcher的656行 -->
            <value>/blog/article/{articleId:\d+}.html</value>
            <!-- 加载文章明细页面的访问次数 -->
            <value>/blog/article/visits/{articleId:\d+}.html</value>
            <!-- 加载文章明细页面的点赞查询 -->
            <value>/blog/article/praises/{articleId:\d+}.html</value>
            <!--加载评论数据-->
            <value>/blog/comment/{targetId:\d+}/{pageNumber:\d+}.html</value>
            <!---->
            <value>/blog/article/type/{articleId:\d+}.html</value>
            <!-- 首页统计图表 -->
            <value>/blog/chart/homepage.html</value>
            <!-- 获取系统统计信息 -->
            <value>/blog/maintenanceInfo.html</value>
            <!--友情链接-->
            <value>/blog/link.html</value>
            <!-- 关于作者、本站介绍、历史上的本站-->
            <value>/blog/aboat/author.html</value>
            <value>/blog/aboat/website.html</value>
            <value>/blog/aboat/history.html</value>
            <!-- 项目介绍 -->
            <value>/blog/project/{key:\w+}.html</value>
            <!-- 系统信息 -->
            <value>/blog/aboat/server.html</value>
            <!-- 标签管理文章查询,路径匹配了存在%20这种中文EncodeURI编码 -->
            <value>/blog/tag/{tag:[^.]+}.html</value>
            <!-- 自定义页面 -->
            <value>/blog/page/{page:\w+}.html</value>
            <!-- 关键字搜一搜页面 -->
            <value>/blog/search.html</value>
            <value>/blog/search/{pageNumber:\d+}.html</value>
        </list>
    </property>
    <!--按请求方式放行,与之匹配到的地址,仅拦截对应请求方式,否则直接放行-->
    <property name="excludeMappingTypes">
        <list>

        </list>
    </property>
</bean>


<!-- 后端系统 -->
<bean id="urlInterceptor" class="cn.chendd.blog.base.spring.component.InterceptorConfiguration">
    <property name="beanClassName" value="cn.chendd.blog.admin.commponents.UrlValidatorInterceptor"/>
    <!--包含过滤的路径-->
    <property name="includeMapping">
        <list>
            <value>/**/*</value>
        </list>
    </property>
    <!--拦截器放行路劲-->
    <property name="excludeMapping">
        <list>
            <value>/</value>
            <value>/login.html</value>
            <value>/error</value>
            <value>/statics/**</value>
            <value>/verificationCode.html</value>
            <value>/v{\d+}/**/*.html</value>
            <!--第三方登录-->
            <value>/v1/third-login/*Callback</value>
        </list>
    </property>
    <!--按请求方式放行,与之匹配到的地址,仅拦截对应请求方式,否则直接放行-->
    <property name="excludeMappingTypes">
        <list>
            <value>/system/login POST</value><!--登录后台地址-->
        </list>
    </property>

</bean>

源码下载

源码工程下载:源码下载.zip

 点赞


 发表评论

当前回复:作者

 评论列表


留言区