什么是拦截器
什么是拦截器:在AOP(Aspect-Oriented Programming)中用于在某个方法或字段被访问之前,进行拦截然后在之前或之后加入某些操作。拦截是AOP的一种实现策略
为什么需要拦截器:在做身份认证或者是进行日志的记录时,我们需要通过拦截器达到我们的目的。最常用的登录拦截、或是权限校验、或是防重复提交、或是根据业务像12306去校验购票时间,总之可以去做很多的事情
如何用拦截器:在spring中用拦截器需要实现HandlerInterceptor接口或者它的实现子类:HandlerInterceptorAdapter,同时在applicationContext.xml文件中配置拦截器
自定义拦截器
配置yml
# 系统配置
sys-config:
#操作项目根路径
file-def-path: D:\
# 拦截器-添加路径
filter-add-path:
# 静态资源
- /**
# 排除路径
filter-excludes-path:
# 静态资源
- /
配置映射类
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* @ClassName SystemConfig
* @Description TODO
*/
@Data
@Component
@ConfigurationProperties(prefix = "sys-config")
public class SystemConfig {
/**
* 文件路径
*/
private String fileDefPath;
/**
* 拦截器拦截路径
*/
private String[] filterAddPath;
/**
* 拦截器排除路径
*/
private String[] filterExcludesPath;
}
后端拦截器
- 新建
SystemeFilter
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.jwt.JWT;
import cn.hutool.jwt.JWTUtil;
import com.example.annotation.AuthLoginAnnotation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
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.lang.reflect.Method;
import java.util.Map;
/**
* @ClassName SystemeFilter
* @Description TODO
*/
@Slf4j
@Configuration
@Order(100) //值越小优先级越高
public class SystemeFilter implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
log.info("request请求地址path[{}] uri[{}] token[{}]", request.getServletPath(), request.getRequestURI(), request.getHeader("token"));
// 如果不是映射到方法直接通过
if (!(handler instanceof HandlerMethod)) {
return true;
}
HandlerMethod handlerMethod = (HandlerMethod) handler;
Method method = handlerMethod.getMethod();
AuthLoginAnnotation authLoginAnnotation = method.getAnnotation(AuthLoginAnnotation.class);
if (authLoginAnnotation == null) {
log.error("后端API,访问缺少注解配置!");
return false;
} else {
if (!authLoginAnnotation.login()) {
// 值为:false时 不进行登录验证 放行
return true;
} else {
//进行登录验证
String token = request.getHeader("token");// 从 http 请求头中取出 token
if (token == null || "".equals(token)) {
token = request.getHeader("Authorization");
if (token == null || "".equals(token)) {
token = BeanUtil.copyProperties(request.getParameter("token"),String.class);
}
}
if (authLoginAnnotation.object().equals("admin")) {
if (token == null) {
response.setStatus(404);
log.error("缺少token,未登录");
throw new Exception("缺少Token,未登录");
} else {
final JWT jwt = JWTUtil.parseToken(token);
if (jwt == null) {
response.setStatus(404);
log.error("token认证失败");
//这里的异常是我自定义的异常,系统抛出异常后框架捕获异常然后转为统一的格式返回给前端, 其实这里也可以返回false
throw new Exception("无效Token");
} else {
//登录成功
//log.error("token认证成功");
return true;
}
}
}
return false;
}
}
}
@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 {
}
}
应用端拦截器
- 新建
AppletFilter
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
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.lang.reflect.Method;
import java.util.Map;
/**
* 自定义-应用程序拦截器
*/
@Slf4j
@Configuration
@Order(2)
public class AppletFilter implements HandlerInterceptor {
/**
* 预处理回调方法,实现处理器的预处理
* 返回值:true表示继续流程;false表示流程中断,不会继续调用其他的拦截器或处理器
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 如果不是映射到方法直接通过
if (!(handler instanceof HandlerMethod)) {
return true;
}
HandlerMethod handlerMethod = (HandlerMethod) handler;
Method method = handlerMethod.getMethod();
boolean annotationPresent = method.isAnnotationPresent(PassToken.class);
//如果有 @PassToken 不进行验证
if (annotationPresent) {
return true;
}
String userToken = request.getHeader(SystemConstants.HEADER_STRING);
String platformId = request.getHeader(SystemConstants.platformId);
if (StringUtils.isBlank(userToken) || !userToken.startsWith(SystemConstants.TOKEN_PREFIX)) {
log.error("小程序没有登录拦截了呦");
throw new NotLogException("小程序没有登录拦截了呦");
//return false;
} else {
final String token = userToken.substring(SystemConstants.TOKEN_PREFIX.length());
try {
WxUserViewModel wxUser = TokenUtil.verifyTokenAndGetUserInfo(token);
if (wxUser == null) {
// throw new NotLogException();
logger.error("小程序没有登录拦截了呦");
throw new NotLogException("小程序没有登录拦截了呦");
}
//转发 设置请求参数
request.setAttribute(SystemConstants.LOGIN_KEY, wxUser);
} catch (final Exception e) {
//
logger.error("小程序没有登录拦截了呦");
throw new NotLogException("小程序没有登录拦截了呦");
}
}
return true;
}
/**
* 后处理回调方法,实现处理器(controller)的后处理,但在渲染视图之前
* 此时我们可以通过modelAndView对模型数据进行处理或对视图进行处理
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
// TODO Auto-generated method stub
}
/**
* 整个请求处理完毕回调方法,即在视图渲染完毕时回调,
* 如性能监控中我们可以在此记录结束时间并输出消耗时间,
* 还可以进行一些资源清理,类似于try-catch-finally中的finally,
* 但仅调用处理器执行链中
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// TODO Auto-generated method stub
}
}
注册拦截器
- 新建
RegisterFilter
import com.example.config.SystemConfig;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
/**
* @ClassName RegisterFilter
* @Description TODO
*/
@Configuration
@RequiredArgsConstructor
public class RegisterFilter extends WebMvcConfigurationSupport {
private final SystemConfig systemConfig;
/**
* 注册拦截器
* @param registry
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
//拦截器一
registry.addInterceptor(new SystemeFilter()).
//排除路径
excludePathPatterns(systemConfig.getFilterExcludesPath()).
//拦截路径
addPathPatterns(systemConfig.getFilterAddPath());
//拦截器二 同理添加其它拦截器
}
/**
* 静态资源处理
* @param registry
*/
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/swagger-ui.html")
.addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/");
registry.addResourceHandler("/**")
.addResourceLocations("classpath:/static/", "classpath:/public/");
super.addResourceHandlers(registry);
}
常用拦截器方法
/** 解决跨域问题 **/
public void addCorsMappings(CorsRegistry registry) ;
/** 添加拦截器 **/
void addInterceptors(InterceptorRegistry registry);
/** 静态资源处理 **/
void addResourceHandlers(ResourceHandlerRegistry registry);
拦截器方法
configurePathMatch:配置路由请求规则
configureContentNegotiation:内容协商配置
configureAsyncSupport
configureDefaultServletHandling:默认静态资源处理器
addFormatters:注册自定义转化器
addInterceptors:拦截器配置
addResourceHandlers:资源处理
addCorsMappings:CORS配置
addViewControllers:视图跳转控制器
configureViewResolvers:配置视图解析
addArgumentResolvers:添加自定义方法参数处理器
addReturnValueHandlers:添加自定义返回结果处理器
configureMessageConverters:配置消息转换器。重载会覆盖默认注册的HttpMessageConverter
extendMessageConverters:配置消息转换器。仅添加一个自定义的HttpMessageConverter.
configureHandlerExceptionResolvers:配置异常转换器
extendHandlerExceptionResolvers:添加异常转化器
getValidator
getMessageCodesResolver
拦截器不起作用问题
继承
WebMvcConfigurationSupport
,重写addInterceptors
方法的类需用到@Configuration
注解addPathPatterns
中的问题。拦截器最后路径一定要 /, 如果是目录的话则是 /*/
配置addCorsMappings跨域无效
这里是因为拦截器的执行顺序,优先级的问题 造成跨域配置没有在拦截器之前注入容器
解决方案 使用 corsFilter拦截器,保证优先进入CorsFilter控制逻辑,保证跨域请求参数到达
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
/**
* @ClassName CorsFilter
* @Description TODO
*/
@Configuration
public class CorsConfig {
@Bean
public FilterRegistrationBean corsFilter() {
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true); // 允许cookies跨域
config.addAllowedOrigin("*");// #允许向该服务器提交请求的URI,*表示全部允许,自定义可以添加多个,在SpringMVC中,如果设成*,会自动转成当前请求头中的Origin
config.addAllowedHeader("*");// #允许访问的头信息,*表示全部,可以添加多个
config.setMaxAge(1800L);// 预检请求的缓存时间(秒),即在这个时间段里,对于相同的跨域请求不会再预检了
config.addAllowedMethod("*");// 允许提交请求的方法,*表示全部允许,一般OPTIONS,GET,POST三个够了
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);//对所有接口都有效
FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source));
bean.setOrder(Ordered.LOWEST_PRECEDENCE); // 优先级最高
return bean;
}
}
用到的一些扩展类
多功能权限注解
AuthLoginAnnotation
import java.lang.annotation.*;
/**
* @author :shanpegnian - http://www.sxpcwlkj.com
* @version :1.0
* @description : com.sxpcwlkj.a_common.annotation
* @date : 2019/3/9
*/
@Documented //文档生成时,该注解将被包含在javadoc中,可去掉
@Target(ElementType.METHOD)//目标是方法
@Retention(RetentionPolicy.RUNTIME) //注解会在class中存在,运行时可通过反射获取
@Inherited
public @interface AuthLoginAnnotation {
/**
* 检查是否已登录(注解的参数)
* @return true-检查;默认true 进行登录验证
*/
boolean login() default true;
/**
* 验证对象 user webmember app ....
* 默认拦截 后台 user对象
* @return
*/
String object() default "user"; //1:user:后台 2:pclm:pc联盟:3:pcgw:pc官网 4:pcjfg:5:pc集福购 6:app:app 7:xcx:小程序 8:gzh:公众号
/**
* 是否验证当前对象 访问权限
* 默认 true 进行验证
* @return
*/
boolean authority() default true;
/**
* 权限标识
* 默认 null 拦截所有
* @return
*/
String authorityCode() default "";
/**
* app是否验证key
* 默认不验证
* @return
*/
boolean key() default false;
}
/*
注解方法不能有参数。
注解方法的返回类型局限于原始类型,字符串,枚举,注解,或以上类型构成的数组。
注解方法可以包含默认值。
注解可以包含与其绑定的元注解,元注解为注解提供信息,有四种元注解类型:
1. @Documented – 表示使用该注解的元素应被javadoc或类似工具文档化,它应用于类型声明,类型声明的注解会影响客户端对注解元素的使用。如果一个类型声明添加了Documented注解,那么它的注解会成为被注解元素的公共API的一部分。
2. @Target – 表示支持注解的程序元素的种类,一些可能的值有TYPE, METHOD, CONSTRUCTOR, FIELD等等。如果Target元注解不存在,那么该注解就可以使用在任何程序元素之上。
3. @Inherited – 表示一个注解类型会被自动继承,如果用户在类声明的时候查询注解类型,同时类声明中也没有这个类型的注解,那么注解类型会自动查询该类的父类,这个过程将会不停地重复,直到该类型的注解被找到为止,或是到达类结构的顶层(Object)。
4. @Retention – 表示注解类型保留时间的长短,它接收RetentionPolicy参数,可能的值有SOURCE(源文件中起作用), CLASS, 以及RUNTIME(保留到运行时起作用)。
*/
PassToken
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author http://www.sxpcwlkj.com
* @version 1.0
* @description com.lhym.applet.config
* @date 2020/11/2
* 自定义注解 用来跳过验证的PassToken
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface PassToken {
boolean required() default true;
}
NoAuthorityException
public class NoAuthorityException extends Exception{
private static final long serialVersionUID = 1L;
public NoAuthorityException(String msg) {
super(msg);
}
public NoAuthorityException(String msg,Throwable cause){
super(msg,cause);
}
public NoAuthorityException(Throwable cause){
super(cause);
}
}
- TokenUtils
verifyTokenAndGetUserInfo
主要是从请求头把Token拿到进行解析,然后根据自定义的Token生成方法,生成一个 用户登录对象getpareJwt
主要是从请求头把Token拿到进行解析,拿到解析的所有参数
验证
访问URL:/admin/user/imageCodeEx
,/admin/user/userLogin
, /api/order/info
, /api/live/room
放行
访问URL:/admin/index
走后端拦截器
访问URL:/api/index
走应用端拦截器