Spring Boot由拦截器HandlerInterceptor实现登录认真、API耗时统计和资源拦截

  背景: 在项目中我使用了自定义的Filter,它过滤了很多路径,当然对静态资源是直接放过去的,但是,还是出现了静态资源没办法访问到Spring Boot默认的文件夹中的文件。另外,经常需要判断当前访问者是否有权操作某个资源。

  博文更新时间:2022-02。

静态资源拦截

  说下默认映射的文件夹有:

  • classpath:/META-INF/resources
  • classpath:/resources
  • classpath:/static
  • classpath:/public

  上面这几个都是静态资源的映射路径,优先级从上向下依次降低。我们可以通过修改spring.mvc.static-path-pattern来修改默认的映射**

***接管Spring Boot 2.5.0 的Web配置 ********* 这是重点中的重点

  如果Spring Boot提供的Sping MVC不符合要求,则可以通过一个配置类(注解有@Configuration的类)加上@EnableWebMvc注解来实现完全自己控制的MVC配置。

  当然,通常情况下,Spring Boot的自动配置是符合我们大多数需求的。在你既需要保留Spring Boot提供的便利,又需要增加额外配置的时候,可以定义一个配置类并继承WebMvcConfigurerAdapter,而无需使用@EnableWebMvc注解。这里我们提到WebMvcConfigurerAdapter类,重写这个类中的方法可以让我们增加额外的配置。

自定义资源映射addResourceHandlers

  比如,我们想自定义静态资源映射目录的话,只需重写addResourceHandlers方法即可。通过addResourceHandler添加映射路径,然后通过addResourceLocations来指定路径。我们访问自定义my文件夹中

@Configuration
public class MyWebMvcConfigurerAdapter extends WebMvcConfigurerAdapter {
    /**
     * 配置静态访问资源
     * @param registry
     */
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/my/**").addResourceLocations("classpath:/my/");
        super.addResourceHandlers(registry);
    }
}

  如果你想指定外部的目录也很简单,直接用addResourceLocations指定即可,代码如下:

public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/my/**").addResourceLocations("file:E:/my/");
        super.addResourceHandlers(registry);
    }

addResourceLocations指的是文件放置的目录,addResoureHandler指的是对外暴露的访问路径。

HandlerInterceptor方法介绍

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:在业务处理器处理请求之前被调用。预处理,可以进行编码、安全控制、权限校验等处理。
postHandle:在业务处理器处理请求执行完成后,生成视图之前执行。它调用Service并返回ModelAndView,但未进行页面渲染。在此处我们有机会修改ModelAndView,但是博主不推荐这么操作。
afterCompletion:在DispatcherServlet处理完请求后被调用,可用于清理资源等。返回已经渲染的页面。

添加拦截器

  拦截器在我们项目中经常使用,这里就来介绍两个最简单的使用场景:①根据session判断用户是否登录,② API执行耗时。要实现拦截器功能需要完成以下2个步骤:

  • 创建拦截器类并实现 HandlerInterceptor 接口
  • 重写WebMvcConfigurationSupport中的addInterceptors方法添加自定义拦截器

  首先,自定义拦截器:


import lombok.extern.slf4j.Slf4j;
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;
import java.time.Duration;
import java.time.Instant;

/**
 * 拦截器
 */
@Slf4j
@Component
public class MyInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        request.setAttribute("starTime", Instant.now());
        boolean flag =true;
        // 判断用户是否登录
        User user=(User)request.getSession().getAttribute("user");
        if(null==user){
            response.sendRedirect("toLogin");
            flag = false;
        }
        return flag;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
          log.info("方法 {} 耗时:--> {}", ((HandlerMethod) handler).getMethod().getName(),
                Duration.between((Instant) request.getAttribute("starTime"), Instant.now()).toMillis());
    }
}

  此段代码简单实现了两个功能:① 根据session中是否有User对象来判断是否登录,为空就跳转到登录页,不为空就通过;② 统计API执行所耗费的时间。接着,重写WebMvcConfigurerAdapter中的拦截器注册方法addInterceptors:

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

import javax.annotation.Resource;

@Configuration
public class ApiConfig extends WebMvcConfigurationSupport {

    @Resource
    private ControllerTimeInterceptor controllerTimeInterceptor;

    @Override
    protected void addInterceptors(InterceptorRegistry registry) {
        // 添加拦截器
        registry.addInterceptor(controllerTimeInterceptor).addPathPatterns("/**")
                .excludePathPatterns("/admin/login", "/admin/getLogin");
        super.addInterceptors(registry);
    }

}

  addPathPatterns("/**")表示拦截所有请求,但是 excludePathPatterns 排除了/toLogin和/login请求的拦截。

  • addInterceptors:添加自定义拦截器的实现逻辑类。
  • addPathPatterns:添加待拦截的请求路径,可以添加多个路径。路径配置规则:/** 匹配所有路径,/**/login/** 匹配包含 /login/ 关键路径的所有路径。
  • excludePathPatterns:添加不需要拦截的请求路径。

  拦截效果如下:

2022-02-19 12:36:38.898 INFO 37941 --- [nio-8087-exec-1] c.s.d.config.ControllerTimeInterceptor : 方法 readConfig 耗时:--> 88

扩展思考

Q:除了使用session认证方式实现登录外,还有哪些做法可以实现自动认证登录?

A:web开发中还常用cookie来做认证登录。也可以使用Spring AOP拦截。

Reference

  1. https://www.cnblogs.com/java-synchronized/p/7091723.html
posted @ 2018-12-02 16:52  楼兰胡杨  阅读(4123)  评论(0编辑  收藏  举报