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拦截。