过滤器与拦截器
文章目录
- 一、前言
- 1、概述
- 2、过滤器与拦截器异同
- 2.1 简介
- 2.2 异同
- 2.3 总结
- 3、Filters vs HandlerInterceptors
- 二、过滤器
- 1、概述
- 2、生命周期
- 2.1 生命周期概述
- 2.2 基于函数回调实现原理
- 3、自定义过滤器两种实现方式
- 3.1 @WebFilter注解注册
- 3.2 过滤器(配置类注册过滤器)
- 4、实战OncePerRequestFilter
- 三、拦截器
- 1、概述
- 2、自定义拦截器
- 2.1 生命周期
- 2.2 代码示例
- 2.3 多拦截器示例
- 3.4 静态资源被拦截问题
- 4、实战demo
一、前言
常用项目编写规范参考:Spring Boot后端接口规范
1、概述
前面讲到数据统一响应、全局异常等常用后端框架,那么随着项目的开发,需要对请求进行校验(参数校验、前面校验等),不符合的不进入后端业务逻辑,提前返回并抛出异常。一般实现方法有拦截器和过滤器,这两者都可以实现对应的功能,可以根据自己喜好进行编写。
过滤器一般完成通用的操作。如:登录验证、统⼀编码处理、敏感字符过滤,常见的过滤器用途主要包括:对用户请求进行统一认证、对用户的访问请求进行记录和审核、对用户发送的数据进行过滤或替换、转换图象格式、对响应内容进行压缩以减少传输量、对请求或响应进行加解密处理、触发资源访问事件等;拦截器采用AOP的设计思想, 它跟过滤器类似, 用来拦截处理方法在之前和之后执行一些 跟主业务没有关系的一些公共功能,比如权限控制、日志、异常记录、记录方法执行时间
下面来讲讲这两者的异同和代码demo。
2、过滤器与拦截器异同
2.1 简介
过滤器(filter)和拦截器(Inteceptor)的执行顺序概览
2.2 异同
- 过滤器和拦截器触发时机不一样,过滤器是在请求进入容器后,但请求进入servlet之前进行预处理的。请求结束返回也是,是在servlet处理完后,返回给前端之前
- 拦截器可以获取IOC容器中的各个bean,而过滤器就不行,因为拦截器是spring提供并管理的,spring的功能可以被拦截器使用,在拦截器里注入一个service,可以调用业务逻辑。而过滤器是JavaEE标准,只需依赖servlet api ,不需要依赖spring
- 过滤器的实现基于回调函数。而拦截器(代理模式)的实现基于反射
- Filter是依赖于Servlet容器,属于Servlet规范的一部分,而拦截器则是独立存在的,可以在任何情况下使用
- Filter的执行由Servlet容器回调完成,而拦截器通常通过动态代理(反射)的方式来执行
- Filter的生命周期由Servlet容器管理,而拦截器则可以通过IoC容器来管理,因此可以通过注入等方式来获取其他Bean的实例,因此使用会更方便
2.3 总结
- 过滤器可以修改request,而拦截器不能
- 过滤器需要在servlet容器中实现,拦截器可以适用于javaEE,javaSE等各种环境
- 拦截器可以调用IOC容器中的各种依赖,而过滤器不能
- 过滤器只能在请求的前后使用,而拦截器可以详细到每个方法
具体的执行调用流程如下
- 过滤器(Filter) :可以拿到原始的http请求,但是拿不到你请求的控制器和请求控制器中的方法的信息
- 拦截器(Interceptor):可以拿到你请求的控制器和方法,却拿不到请求方法的参数
- 切片(Aspect): 可以拿到方法的参数,但是却拿不到http请求和响应的对象
这里说一下为什么spring security使用过滤器而不是拦截器。因为作为一个通用的安全框架不应该耦合其他web框架的元素。很显然拦截器是spring mvc或struts等框架提供的,如果基于拦截器势必耦合这些框架,就做不到通用了
3、Filters vs HandlerInterceptors
- Filter 是 Servlet 规范中的,而 HandlerInterceptor 是 Spring 中的一个概念
- 拦截器位置相对于过滤器更靠后
- 精细的预处理任务适用于拦截器,如授权检查等
- 内容处理相关或通用的流程,非常适合用过滤器;如上传表单、zip 压缩、图像处理、日志记录请求、身份验证等
- HandlerInterceptor 的
postHandle
方法允许我们向视图添加更多模型对象,但不能更改 HttpServletResponse,因为它已经被提交了 - 过滤器的
doFilter
方法比拦截器的postHandle
更通用。我们可以在过滤器中改变请求或响应,并将其传递给链,甚至阻止请求的处理 - HandlerInterceptor 提供了比过滤器更精细的控制,因为我们可以访问实际的目标 handler,甚至可以检查 handler 方法是否有某个特定的注解
二、过滤器
1、概述
过滤器(Filter)是处于客户端与服务器目标资源之间的⼀道过滤技术,当访问服务器的资源时,过滤器可以将请求拦截下来,完成⼀些特殊的功能
执行是在Servlet之前,客户端发送请求时,会先经过Filter,再到达目标Servlet中;响应时, 会根据执行流程再次反向执行Filter,⼀般用于完成通用的操作。如:登录验证、统⼀编码处理、敏感字符过滤。常见的过滤器用途主要包括:对用户请求进行统一认证、对用户的访问请求进行记录和审核、对用户发送的数据进行过滤或替换、转换图象格式、对响应内容进行压缩以减少传输量、对请求或响应进行加解密处理、触发资源访问事件等
2、生命周期
2.1 生命周期概述
过滤器的配置比较简单,直接实现Filter 接口即可,也可以通过@WebFilter
注解实现对特定URL拦截,看到Filter 接口中定义了三个方法。
init()
:该方法在容器启动初始化过滤器时被调用,它在 Filter 的整个生命周期只会被调用一次。注意:这个方法必须执行成功,否则过滤器会不起作用doFilter()
:容器中的每一次请求都会调用该方法,比如定义一个 Filter 拦截/path/*
,那么每一个匹配/path/*
访问资源的请求进来时,都会执行此方法, FilterChain 用来调用下一个过滤器 Filter。不同的过滤器通过@Order()
排序注解执行顺序destroy()
: 当容器销毁 过滤器实例时调用该方法,一般在方法中销毁或关闭资源,在过滤器 Filter 的整个生命周期也只会被调用一次
2.2 基于函数回调实现原理
在我们自定义的过滤器中都会实现一个 doFilter()
方法,这个方法有一个FilterChain
参数,而实际上它是一个回调接口。ApplicationFilterChain
是它的实现类, 这个实现类内部也有一个 doFilter()
方法就是回调方法。
public interface FilterChain {void doFilter(ServletRequest var1, ServletResponse var2) throws IOException, ServletException;
}
ApplicationFilterChain
里面能拿到我们自定义的xxxFilter
类,在其内部回调方法doFilter()
里调用各个自定义xxxFilter
过滤器,并执行 doFilter()
方法
public final class ApplicationFilterChain implements FilterChain {@Overridepublic void doFilter(ServletRequest request, ServletResponse response) {...//省略internalDoFilter(request,response);}private void internalDoFilter(ServletRequest request, ServletResponse response){if (pos < n) {//获取第pos个filter ApplicationFilterConfig filterConfig = filters[pos++]; Filter filter = filterConfig.getFilter();...filter.doFilter(request, response, this);}}}
而每个xxxFilter
会先执行自身的 doFilter()
过滤逻辑,最后在执行结束前会执行filterChain.doFilter(servletRequest, servletResponse)
,也就是回调ApplicationFilterChain
的doFilter()
方法,以此循环执行实现函数回调
3、自定义过滤器两种实现方式
不论是注解配置还是Java配置,都需要在启动类上加上@ServletComponentScan("过滤器路径")
注解,过滤路径可以不写(或者直接注入容器交给spring管理)。注解注册和Java配置类注册,它们的自定义过滤器类都是一样的,只不过注册过程一个是通过@WebFilter
注解,一个是通过Java配置类注册Bean。
3.1 @WebFilter注解注册
/*** 自定义注解过滤器实现* Filter的包是javax.servlet.Filter的* filterName:过滤器名称,需要唯一,不能重复* urlPatterns:要拦截的url资源路径,注意:通配符是一个星号(*)*/
@Order(2)//排序注解,执行顺序
@WebFilter(filterName = "filterAnnotation",urlPatterns = {"/study/interfaces/v1/user"})
public class filterAnnotation implements Filter {//初始化操作,只会执行一次@Overridepublic void init(FilterConfig filterConfig) throws ServletException {System.out.println("filterAnnotation--初始化Filter");}//进入到过滤资源之前和之后做的事情@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {System.out.println("filterAnnotation--进入Target Resource之前做的事情");filterChain.doFilter(servletRequest,servletResponse);System.out.println("filterAnnotation--处理返回的Response");}//销毁,只会在项目停止或者重新部署的时候才会执行@Overridepublic void destroy() {System.out.println("filterAnnotation--销毁Filter");}
}
再举一个例子
/*** 检查用户是否已经完成登录*/
@WebFilter(filterName = "loginCheckFilter",urlPatterns = "/*")
@Slf4j
public class LoginCheckFilter implements Filter{//路径匹配器,支持通配符public static final AntPathMatcher PATH_MATCHER = new AntPathMatcher();@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {HttpServletRequest request = (HttpServletRequest) servletRequest;HttpServletResponse response = (HttpServletResponse) servletResponse;//1、获取本次请求的URIString requestURI = request.getRequestURI();// /backend/index.htmllog.info("拦截到请求:{}",requestURI);//定义不需要处理的请求路径String[] urls = new String[]{"/employee/login","/employee/logout","/backend/**","/front/**","/common/**"};//2、判断本次请求是否需要处理boolean check = check(urls, requestURI);//3、如果不需要处理,则直接放行if(check){log.info("本次请求{}不需要处理",requestURI);filterChain.doFilter(request,response);return;}//4、判断登录状态,如果已登录,则直接放行if(request.getSession().getAttribute("employee") != null){log.info("用户已登录,用户id为:{}",request.getSession().getAttribute("employee"));Long empId = (Long) request.getSession().getAttribute("employee");BaseContext.setCurrentId(empId);filterChain.doFilter(request,response);return;}log.info("用户未登录");//5、如果未登录则返回未登录结果,通过输出流方式向客户端页面响应数据response.getWriter().write(JSON.toJSONString(R.error("NOTLOGIN")));return;}/*** 路径匹配,检查本次请求是否需要放行*/public boolean check(String[] urls,String requestURI){for (String url : urls) {boolean match = PATH_MATCHER.match(url, requestURI);if(match){return true;}}return false;}
}
3.2 过滤器(配置类注册过滤器)
public class BaseFilter implements Filter {Logger logger = LoggerFactory.getLogger(BaseFilter.class);static final String TOKEN = "20220423344556abac";//内部接口集合public static List<String> INSIDE_URLS = Lists.newArrayList("/index","/inside");//白名单接口集合public static List<String> WHITE_PATH = Lists.newArrayList("/white","/login");@Overridepublic void init(FilterConfig filterConfig) throws ServletException {logger.info("初始化数据");}@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {HttpServletResponseWrapper wrapper = new HttpServletResponseWrapper((HttpServletResponse)servletResponse);HttpServletRequest request = (HttpServletRequest) servletRequest;String requestURI = request.getRequestURI();if(INSIDE_URLS.contains(requestURI)){//内部接口,直接通过filterChain.doFilter(servletRequest,servletResponse);return;}if(WHITE_PATH.contains(requestURI)){//白名单接口,直接通过filterChain.doFilter(servletRequest,servletResponse);return;}//进行校验,如token校验String token = request.getHeader("token");if(TOKEN.equals(token)){filterChain.doFilter(servletRequest,servletResponse);}else {//token校验不通过,重定向到登录页面wrapper.sendRedirect("/login");}}@Overridepublic void destroy() {}
}
然后设置配置类
/*** 作用相当于@WebFilter这个注解* 过滤器配置类,进过滤器配置到bean中* Filter的包是javax.servlet.Filter的*/
@Configuration//这个注解的目的是被IOC容器获取到
public class FilterConfig {/*** 基础过滤器* @return*/@Beanpublic FilterRegistrationBean<Filter> baseFilter(){FilterRegistrationBean<Filter> filterRegistrationBean = new FilterRegistrationBean();filterRegistrationBean.setFilter(new BaseFilter());//注册自定义过滤器类//过滤资源的路径,或者静态资源,注意:通配符是一个星号(*)filterRegistrationBean.setUrlPatterns(Lists.newArrayList("/*"));filterRegistrationBean.setOrder(1);//排序return filterRegistrationBean;}
}
4、实战OncePerRequestFilter
自定义配置类
@Data
@Configuration
@ConfigurationProperties(prefix = "security.checker")
public class SecurityCheckerConfig {private Boolean enable;/*** 存放accessKey和accessSecurity*/private Map<String, String> maps;/*** sign的过期时间*/private Integer signExpireTime;
}
下面继承了OncePerRequestFilter 来实现我们自己的自定义过滤器,OncePerRequestFilter 特点是请求进入后只会过滤一次,不会重复过滤(有些情况请求可能会两次进入相同的过滤器),同时在不符合要求的请求需要即使抛出异常返回,或者重定向到其他接口
@Component
@Slf4j
public class ParamCheckFilter extends OncePerRequestFilter {@Autowired@Qualifier("handlerExceptionResolver")private HandlerExceptionResolver resolver;// 自己在application.yaml定义的字段@Resourceprivate SecurityCheckerConfig securityCheckerConfig;@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {if(!securityCheckerConfig.getEnable()){filterChain.doFilter(request,response);return;}String timestamp = request.getHeader("timestamp");String accessKey = request.getHeader("accesskey");String sign = request.getHeader("sign");//根据key在配置文件拿取accessSecretString accessSecret = securityCheckerConfig.getMaps().get(accessKey);//检查时间戳合法性if(!StringUtils.isNumeric(timestamp)){// 异常类自定义的resolver.resolveException(request,response,null,new TruckException(CustomCodeEnum.TIMESTAMP_IS_WRONGFUL));return;}//禁止超时签名Long ts = Long.valueOf(timestamp);if (System.currentTimeMillis() - ts > (securityCheckerConfig.getSignExpireTime() * CommonConstant.SECOND_TO_MILLIS)) {resolver.resolveException(request,response,null,new TruckException(CustomCodeEnum.SIGN_OVERTIME));return;}// 检查KEY是否合理if (StringUtils.isBlank(accessKey) || StringUtils.isBlank(accessSecret)) {resolver.resolveException(request,response,null,new TruckException(CustomCodeEnum.ACCESSKEY_WRONGFUL));return;}if(!checkSign(getBody(request),accessSecret, sign)){resolver.resolveException(request,response,null,new TruckException(CustomCodeEnum.SIGN_ERROR));return;}filterChain.doFilter(request,response);}private boolean checkSign(Map<String, Object> params, String accessSecret, String originSign) {String sign = createSign(params, accessSecret);if (!sign.equals(originSign)) {log.error("sign 校验不通过! params: {}, ours sign : {}, theirs : {}", params, sign, originSign);return false;}return true;}/*** 获取请求体(去除空值)*/private LinkedHashMap<String, Object> getBody(HttpServletRequest request) {Map<String, String[]> requestParameterMap = request.getParameterMap();JSONObject params = new JSONObject();if(!CollectionUtils.isEmpty(requestParameterMap)){requestParameterMap.forEach((k,v) -> params.put(k,v[0]));}return sortFields(params);}/*** 将请求参数按照ASCII码排序,方便校验sign*/private LinkedHashMap<String, Object> sortFields(JSONObject params) {// 将请求参数按照ASCII码排序,方便校验signString json = JSON.toJSONString(params, SerializerFeature.SortField);return JSONObject.parseObject(json, LinkedHashMap.class, Feature.OrderedField);}/*** 生成sign* @param params 所有字段按照ASCII码排序,否则签名不一样*/public String createSign(Map<String, Object> params, String accessSecret) {Set<String> keysSet = params.keySet();Object[] keys = keysSet.toArray();Arrays.sort(keys);// 拼接所有一级字段,二级字段不处理,但是字段按ASCII码排序List<String> paramList = new ArrayList<>();for (Object key : keys) {String value = String.valueOf(params.get(key));String str = key + "=" + value.replaceAll("[\"| ]","").replaceAll(":", "").trim();paramList.add(str);}paramList.add("accessSecret=" + accessSecret);String paramStr = String.join("&", paramList);return DigestUtils.md5DigestAsHex(paramStr.getBytes()).toUpperCase();
// return DigestUtils.md5Hex(paramStr).toUpperCase();}
}
这里要注意的是我们用了HandlerExceptionResolver
,因为在Spring Boot由于全局异常处理@RestControllerAdvice
只会去捕获所有Controller层抛出的异常,所以在filter当中抛出的异常GlobalExceptionHandler类是没有感知的,所以在filter当中抛出的异常最终会被Spring框架自带的全局异常处理类BasicErrorController捕获,会返回基础格式的Json响应
一种方法是继承上面所说的BasicErrorController类
,并重写error()方法;另一种就是在filter当中引入HandlerExceptionResolver
类,通过该类的resolveException
方法抛出自定义异常,通过resolveException方法抛出的自定义异常可以被RestControllerAdvice
捕获,从而满足我们的需求,最终得到的响应格式
三、拦截器
1、概述
拦截器采用AOP的设计思想, 它跟过滤器类似, 用来拦截处理方法在之前和之后执行一些 跟主业务没有关系的一些公共功能,比如可以实现权限控制、日志、异常记录、记录方法执行时间等等
2、自定义拦截器
2.1 生命周期
SpringMVC提供了拦截器机制,允许运行目标方法之前进行一些拦截工作或者目标方法运行之后进行一下其他相关的处理。自定义的拦截器必须实现 HandlerInterceptor接口。
HandlerInterceptor 接口中定义了三个方法
preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
:这个方法将在请求处理之前进行调用。注意:如果该方法的返回值为false ,将视为当前请求结束,不仅自身的拦截器会失效,还会导致其他的拦截器也不再执行。postHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
:只有在 preHandle() 方法返回值为true 时才会执行。会在Controller 中的方法调用之后,DispatcherServlet 返回渲染视图之前被调用。 此时我们可以通过modelAndView(模型和视图对象)对模型数据进行处理或对视图进行处理,modelAndView也可能为nullafterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler)
:只有在 preHandle() 方法返回值为true 时才会执行。在整个请求结束之后, DispatcherServlet 渲染了对应的视图之后执行。如性能监控中我们可以在此记录结束时间并输出消耗时间,还可以进行一些资源清理,类似于try catch finally
中的finally,但仅调用处理器执行链中preHandle返回true的拦截器才会执行
2.2 代码示例
首先创建自定义拦截器
public class MyFirstInterceptor implements HandlerInterceptor {/*** 在处理方法之前执 日志、权限、 记录调用时间* @param request 可以在方法请求进来之前更改request中的属性值* @param response* @param handler 封装了当前处理方法的信息* @return true 后续调用链是否执行/ false 则中断后续执行* @throws Exception*/@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 在请求映射到对应的处理方法映射,实现类才是HandlerMethod。// 如果是视图控制器,实现类ParameterizableViewControllerif(handler instanceof HandlerMethod ) {HandlerMethod handMethod = (HandlerMethod) handler;}/*System.out.println("-------类["+handMethod.getBean().getClass().getName()+"]" +"方法名["+handMethod.getMethod().getName()+"]" +"参数["+ Arrays.toString(handMethod.getMethod().getParameters()) +"]前执行--------preHandle");*/System.out.println(this.getClass().getName()+"---------方法后执行,在渲染之前--------------preHandle");return true;}/*** 如果preHandle返回false则会不会允许该方法* 在请求执行后执行, 在视图渲染之前执行* 当处理方法出现了异常则不会执行方法* @param request* @param response 可以在方法执行后去更改response中的信息* @param handler 封装了当前处理方法的信息* @param modelAndView 封装了model和view.所以当请求结束后可以修改model中的数据或者新增model数据,也可以修改view的跳转* @throws Exception*/@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {System.out.println(this.getClass().getName()+"---------方法后执行,在渲染之前--------------postHandle");}/*** 如果preHandle返回false则会不会允许该方法* 在视图渲染之后执行,相当于try catch finally 中finally,出现异常也一定会执行该方法* 如果执行的时候核心的业务代码出问题了,那么已经通过的拦截器的 afterCompletion会接着执行* @param ex Exception对象,在该方法中去做一些:记录异常日志的功能,或者清除资源* @throws Exception*/@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {System.out.println(this.getClass().getName()+"---------在视图渲染之后--------------afterCompletion");}
}
然后在spring boot 项目中配置,实现 WebMvcConfigurer
接口 并重写 addInterceptors
方法
@Configuration
public class InterceptorAdapter implements WebMvcConfigurer {@Beanpublic MyFirstInterceptor myInterceptor(){return new MyFirstInterceptor();}public void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(myInterceptor()).addPathPatterns("/**").excludePathPatterns("/*.html");}
}
@RequestMapping("/test01")public String test01(){System.out.println("请求方法执行中...");return "admin";}
}
2.3 多拦截器示例
拦截顺序取决于配置的顺序
@Configuration
public class InterceptorAdapter implements WebMvcConfigurer {@Beanpublic MyFirstInterceptor myInterceptor(){return new MyFirstInterceptor();}@Beanpublic MySecondInterceptor mySecondInterceptor(){return new MySecondInterceptor();}public void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(myInterceptor()).addPathPatterns("/**").excludePathPatterns("/*.html");registry.addInterceptor(mySecondInterceptor()).addPathPatterns("/**").excludePathPatterns("/*.html").order(1);}
}
看到输出结果发现,先声明的拦截器 preHandle() 方法先执行,而postHandle()方法反而会后执行。但注意postHandle() 方法被调用的顺序跟 preHandle() 是相反的。我们要知道controller 中所有的请求都要经过核心组件DispatcherServlet路由,都会执行它的 doDispatch()
方法,而拦截器postHandle()、preHandle()方法便是在其中调用的。查看源码可知,发现两个方法中在调用拦截器数组 HandlerInterceptor[] 时,循环的顺序竟然是相反的
3.4 静态资源被拦截问题
配置拦截器会导致静态资源被拦截,比如在 resources/static/ 目录下放置一个图片资源或者 html 文件,然后启动项目直接访问,即可看到无法访问的现象。也就是说,虽然 Spring Boot 2.0 废弃了WebMvcConfigurerAdapter
,但是 WebMvcConfigurationSupport
又会导致默认的静态资源被拦截,这就需要我们手动将静态资源放开
除了在 MyInterceptorConfig 配置类中重写 addInterceptors
方法外,还需要再重写一个方法:addResourceHandlers
,将静态资源放开
/*** 用来指定静态资源不被拦截,否则继承WebMvcConfigurationSupport这种方式会导致静态资源无法直接访问* @param registry*/
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {registry.addResourceHandler("/**").addResourceLocations("classpath:/static/");super.addResourceHandlers(registry);
}
这样配置好之后,重启项目,静态资源也可以正常访问了。
另一种方法是不继承 WebMvcConfigurationSupport
类,直接实现 WebMvcConfigurer
接口,然后重写 addInterceptors
方法,将自定义的拦截器添加进去即可(上面讲到)
@Configuration
public class MyInterceptorConfig implements WebMvcConfigurer {@Overridepublic void addInterceptors(InterceptorRegistry registry) {// 实现WebMvcConfigurer不会导致静态资源被拦截registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**");}
}
4、实战demo
判断用户有没有登录,一般用户登录功能我们可以这么做,要么往 session 中写一个 user,要么针对每个 user 生成一个 token,第二种要更好一点,那么针对第二种方式,如果用户登录成功了,每次请求的时候都会带上该用户的 token,如果未登录,则没有该 token,服务端可以检测这个 token 参数的有无来判断用户有没有登录,从而实现拦截功能。我们改造一下 preHandle
方法
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {HandlerMethod handlerMethod = (HandlerMethod) handler;Method method = handlerMethod.getMethod();String methodName = method.getName();logger.info("====拦截到了方法:{},在该方法执行之前执行====", methodName);// 判断用户有没有登陆,一般登陆之后的用户都有一个对应的tokenString token = request.getParameter("token");if (null == token || "".equals(token)) {logger.info("用户未登录,没有权限执行……请登录");return false;}// 返回true才会继续执行,返回false则取消当前请求return true;
}
最后还有一个监听器,可以参考:Spring事件监听
https://zhuanlan.zhihu.com/p/340397290
https://zhuanlan.zhihu.com/p/484289805
https://www.zhihu.com/question/443466900/answer/2509838187
https://blog.csdn.net/qq_45534061/article/details/106266747
https://blog.csdn.net/m0_37731470/article/details/116754395
https://blog.csdn.net/xinzhifu1/article/details/106356958/
相关文章:

过滤器与拦截器
文章目录一、前言1、概述2、过滤器与拦截器异同2.1 简介2.2 异同2.3 总结3、Filters vs HandlerInterceptors二、过滤器1、概述2、生命周期2.1 生命周期概述2.2 基于函数回调实现原理3、自定义过滤器两种实现方式3.1 WebFilter注解注册3.2 过滤器(配置类注册过滤器&…...
spring boot 和cloud 版本升级
spring boot 和cloud 版本对应 背景:原来一直用的版本是Hoxton.SR12 2.3.10.RELEASE(SR12一路升,几乎没有影响,不需要测试,但是换大版本就有点担心) 去年2022年底黑鸭子报漏洞把springboot,clou…...

untiy 录制网络摄像头视频并保存到本地文件
网络摄像头使用的是海康威视的,关于如何使用Ump插件播放海康威视rtsp视频流,请参考我的这篇文章 内部有ump插件的下载链接 untiy接入 海康威视网络摄像头 录屏使用的插件是 AVPro movieCapture 4.6.3版, 插件和完整工程的下载链接放在本文的…...
微服务架构设计模式-(15)部署
关联概念 流程 将软件投入到生产环境 架构 软件运行的环境结构 生产环境四个关键功能 服务管理接口 使开发人员能够创建、更新和配置服务 运行时服务管理 确保始终运行一定数量的服务实例非中断更新 监控 让开发人员了解服务情况,包括日志文件和各种应用指标可观…...

Redis:数据结构
简单动态字符串SDS Redis没有直接使用C语言传统的字符串表示(以空字符结尾的字符数组,以下简称C字符串),而是自己构 建了一种名为简单动态字符串(simple dynamic string, SDS)的抽象类型,并将SDS用作Redis的默认字符 串表示。 SDS 的实现…...

2.18 设置language和中文输入法
文章目录一:设置language二:设置中文输入法一:设置language nvidia的开发板上默认只有English,需要点击如下管理: 接着进入如下界面: 此时图中的“汉语(中国)”应该是没有的&…...

图解LeetCode——剑指 Offer 28. 对称的二叉树
一、题目 请实现一个函数,用来判断一棵二叉树是不是对称的。如果一棵二叉树和它的镜像一样,那么它是对称的。 二、示例 2.1> 示例 1: 【输入】root [1,2,2,3,4,4,3] 【输出】true 2.2> 示例 2: 【输入】root [1,2,2,nul…...

Qt Desginer布局方法
首先将我们需要的控件拖拽到一个合适的位置,该例子中用到了两个label,两个lineEdit和两个pushButton。 然后我们需要利用弹簧来控制控件到控件之间的距离以及控件到窗体边界的距离,因为这里只有一组控件(两个label,两个…...
C/C++、Java、Python的比较及学习(3)
函数间的值传递与地址传递 值传递方式:指主调函数把实参的值赋给形参。 在这种传递方式下,主调函数中的实参地址与被调函数中的形参地址是相互独立的。 函数被调用时,系统为形参变量分配内存单元,并将实参的值存入到对应形参的内存…...
智慧校园建设方案
第一章、 智慧教学 6.1. 校本资源库 提供校本资源管理功能,实现学校内的教学资源的共建共享,促进教师之间的交流学习,提升学校的整体教学水平。在本系统中学校可以统一采购资源接入到校本资源库中供教师下载使用,教师也可以将…...

ARM uboot 源码分析5 -启动第二阶段
一、start_armboot 解析6 1、console_init_f (1) console_init_f 是 console(控制台)的第一阶段初始化。_f 表示是第一阶段初始化,_r 表示第二阶段初始化。有时候初始化函数不能一次一起完成,中间必须要夹杂一些代码,…...

【ip neigh】管理IP邻居( 添加ARP\NDP静态记录、删除记录、查看记录)
一、邻居管理存在状态 1、NUD_NONE: 初始状态。当一个新的路由缓存条目被创建时,arp_bind_neighbour()函数被调用.如果找不到相匹配的ARP缓存条目, neigh_alloc()将创建一个新的ARP缓存条目并设置状态为NUD_NONE. 2、NUD_INCOMPLETE:未完成状…...

Java程序员线上排查问题神器-Arthas
文章目录前言一、Arthas是什么?二、快速入门1.下载2.如何运行三、常用命令1.dashboard2.trace总结前言 最近公司项目版本迭代升级,在开发新需求导致没什么时间写博客。 在开发需求的过程中,我写了一个接口,去批量调内部已经写好…...

上市公司企业持续创新能力、创新可持续性(原始数据+计算代码+计算结果)(2008-2021年)
数据来源:自主计算 时间跨度:2008-2021年 区域范围:沪深A股上市公司 指标说明: 参考何郁冰(2017)[1]的做法,将持续创新作为独立研究变量,同时采用创新投入指标(研发经费) 和创新…...
华为OD机试 - 考古学家(JS)
考古学家 题目 有一个考古学家发现一个石碑 但是很可惜 发现时其已经断成多段 原地发现N个断口整齐的石碑碎片 为了破解石碑内容 考古学家希望有程序能帮忙计算复原后的石碑文字组合数 你能帮忙吗 备注: 如果存在石碑碎片内容完全相同,则由于碎片间的顺序不影响复原后的碑…...
Leetcode.2100 适合打劫银行的日子
题目链接 Leetcode.2100 适合打劫银行的日子 Rating : 1702 题目描述 你和一群强盗准备打劫银行。给你一个下标从 0开始的整数数组 security,其中 security[i]是第 i天执勤警卫的数量。日子从 0开始编号。同时给你一个整数 time。 如果第 i天满足以下所…...

linux ubuntu查日志信息以及错误排查
目录 一、linux的日志文件 1、常用日志文件 2、其他日志文件 二、历史日志的查看 1、查看Logrotate的配置信息 2、查看日志配置 一、linux的日志文件 Linux系统中最有趣的(可能也是最重要的)目录之一是/var/log。根据文件系统层次结构标准,在系统中运行的大多数…...

DOS经典软件,落下帷幕,新型国产平台,蓬勃发展
提起DOS时代,总让人难以忘怀,陷入深深回忆中,风靡一时的许多软件,如今早已不在,这几款被称为DOS必装的软件,更是让人惋惜。 你还记得这图吗?堪称DOS系统最经典的软盘复制与映像生成软件…...

MongoDB数据存储格式
前言 之前分享了MongoDB的基本命名和视图等信息,本文分享一下MongoDB的数据存储类型,使用方式。基础的MongoDB信息就学习完啦,之后会继续分享MongoDB进阶的一些东西。 MongoDB数据存储格式前言1 文件结构1.2 字段名称2 点符号2.2 嵌入式文件…...
ARC126D Pure Straight
ARC126D Pure Straight 题目大意 给一个长度为nnn的整数序列A(a1,a2,…,an)A(a_1,a_2,\dots,a_n)A(a1,a2,…,an),其中ai∈[1,k]a_i\in [1,k]ai∈[1,k]。 你可以做如下操作任意次: 交换相邻两个元素 求最小的操作次数,使得序列AA…...

深入浅出Asp.Net Core MVC应用开发系列-AspNetCore中的日志记录
ASP.NET Core 是一个跨平台的开源框架,用于在 Windows、macOS 或 Linux 上生成基于云的新式 Web 应用。 ASP.NET Core 中的日志记录 .NET 通过 ILogger API 支持高性能结构化日志记录,以帮助监视应用程序行为和诊断问题。 可以通过配置不同的记录提供程…...

CTF show Web 红包题第六弹
提示 1.不是SQL注入 2.需要找关键源码 思路 进入页面发现是一个登录框,很难让人不联想到SQL注入,但提示都说了不是SQL注入,所以就不往这方面想了 先查看一下网页源码,发现一段JavaScript代码,有一个关键类ctfs…...

从WWDC看苹果产品发展的规律
WWDC 是苹果公司一年一度面向全球开发者的盛会,其主题演讲展现了苹果在产品设计、技术路线、用户体验和生态系统构建上的核心理念与演进脉络。我们借助 ChatGPT Deep Research 工具,对过去十年 WWDC 主题演讲内容进行了系统化分析,形成了这份…...

跨链模式:多链互操作架构与性能扩展方案
跨链模式:多链互操作架构与性能扩展方案 ——构建下一代区块链互联网的技术基石 一、跨链架构的核心范式演进 1. 分层协议栈:模块化解耦设计 现代跨链系统采用分层协议栈实现灵活扩展(H2Cross架构): 适配层…...
C++ 基础特性深度解析
目录 引言 一、命名空间(namespace) C 中的命名空间 与 C 语言的对比 二、缺省参数 C 中的缺省参数 与 C 语言的对比 三、引用(reference) C 中的引用 与 C 语言的对比 四、inline(内联函数…...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...

Mac下Android Studio扫描根目录卡死问题记录
环境信息 操作系统: macOS 15.5 (Apple M2芯片)Android Studio版本: Meerkat Feature Drop | 2024.3.2 Patch 1 (Build #AI-243.26053.27.2432.13536105, 2025年5月22日构建) 问题现象 在项目开发过程中,提示一个依赖外部头文件的cpp源文件需要同步,点…...

Aspose.PDF 限制绕过方案:Java 字节码技术实战分享(仅供学习)
Aspose.PDF 限制绕过方案:Java 字节码技术实战分享(仅供学习) 一、Aspose.PDF 简介二、说明(⚠️仅供学习与研究使用)三、技术流程总览四、准备工作1. 下载 Jar 包2. Maven 项目依赖配置 五、字节码修改实现代码&#…...

NXP S32K146 T-Box 携手 SD NAND(贴片式TF卡):驱动汽车智能革新的黄金组合
在汽车智能化的汹涌浪潮中,车辆不再仅仅是传统的交通工具,而是逐步演变为高度智能的移动终端。这一转变的核心支撑,来自于车内关键技术的深度融合与协同创新。车载远程信息处理盒(T-Box)方案:NXP S32K146 与…...

排序算法总结(C++)
目录 一、稳定性二、排序算法选择、冒泡、插入排序归并排序随机快速排序堆排序基数排序计数排序 三、总结 一、稳定性 排序算法的稳定性是指:同样大小的样本 **(同样大小的数据)**在排序之后不会改变原始的相对次序。 稳定性对基础类型对象…...