过滤器与拦截器
文章目录
- 一、前言
- 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…...
基于RK3588的嵌入式linux系统开发(四)——uboot镜像下载(基于RKDevTool工具)
官方提供的SDK中包含RKDevTool工具(RKDevTool_Release_v2.92)和相应的驱动(DriverAssitant_v5.1.1)。本节主要介绍在windows操作系统环境下利用RKDevTool下载以上生成的uboot镜像和bootloader镜像。注意:本节使用的板卡…...
设计模式之策略模式与责任链模式详解和应用
目录1.策略模式1.1 目标1.2.内容定位1.3.定义1.4.应用场景1.5.促销优惠业务场景1.6 用策略模式实现选择支付方式的业务场景1.7 策略模式在框架源码中的体现1.8 策略模式的优缺点2 责任链模式2.1 责任链楼式的应用场景2.2 利用责任链模式进行数据校验拦截2.3 责任链模式和建造者…...
广度优先搜索(BFS)-蓝桥杯
一、BFS搜索的原理BFS搜索的原理:“逐层扩散”,从起点出发,按层次从近到远,逐层先后搜索。编码:用队列实现。应用:BFS一般用于求最短路径问题,BFS的特点是逐层搜索,先搜到的层离起点…...
Java Type类
文章目录Type简介Type分类1. 原始类型(Class)2. 参数化类型(ParameterizedType)3. 类型变量(TypeVariable)4. 通配符类型(WildcardType)5. 泛型数组类型(GenericArrayType)Type简介 Type是Java编程语言中所有类型的公共高级接口。它们包括原始类型、参数化类型、数组类型、类型…...
Springboot扩展点之CommandLineRunner和ApplicationRunner
Springboot扩展点系列:Springboot扩展点之ApplicationContextInitializerSpringboot扩展点之BeanFactoryPostProcessorSpringboot扩展点之BeanDefinitionRegistryPostProcessorSpringboot扩展点之BeanPostProcessorSpringboot扩展点之InstantiationAwareBeanPostPro…...
ngixn 常用配置之文件类型与自定义 log
大家好,我是 17 。 总结了一些 nginx 的常用配置。从入口文件开始,今天讲一下文件类型和自定义log 为了讲述方便,环境为 CentOS 7, nginx 版本 1.21。 配置文件入口 /etc/nginx/nginx.conf这是入口文件,这个文件里…...
【100个 Unity实用技能】 | Unity 通过自定义菜单将资源导出
Unity 小科普 老规矩,先介绍一下 Unity 的科普小知识: Unity是 实时3D互动内容创作和运营平台 。包括游戏开发、美术、建筑、汽车设计、影视在内的所有创作者,借助 Unity 将创意变成现实。Unity 平台提供一整套完善的软件解决方案ÿ…...
0.3调试opencv源码的两种方式
调试opencv源码的两种方式 上两篇我们分别讲了如何配置opencv环境,以及如何编译opencv源码方便我们阅读。但我们还是无法调试我们的代码,无法以我们的程序作为入口来一步一步单点调试看opencv是如何执行的。 【opencv源码解析0.1】VS如何优雅的配置ope…...
Redis的常见操作和Session的持久化
安装Redis使用yum命令,直接将redis安装到linux服务器:yum -y install redis启动redis使用以下命令,以后台运行方式启动redis:redis -server /etc/redis.conf &操作redis使用以下命令启动redis客户端:redis-cli设置…...
TypeScript笔记(二)
背景 上一篇文章我们介绍了TypeScript的一些特性,主要是其与JavaScript的比较,接下来我们将会开始学习Type的语法,这篇文章将会介绍TypeScript的数据类型。 原始数据类型 TypeScript是JavaScript的超集,TypeScript的数据类型就…...
如何判断网站被google k/产品设计
字符串 1.单引号和双引号都一样2.单引号可以嵌套双引号,双引号可以嵌套单引号3.单单不能嵌套,双双不能嵌套,需要嵌套怎么办在里面的符号前面加上\用来转义4.charAt(n),打印第n几个数字,数字从0开始,如果参数大于最大长度或者负数,那么返回空字符串5.concat链接字符串 s1"…...
网络维护好学吗/网站页面优化方案
GotW #04 Class Mechanics 著者:Herb Sutter 翻译:kingofark [声明]:本文内容取自www.gotw.ca网站上的Guru of the Week栏目,其著作权归原著者本人所有。译者kingofark在未经原著者本人同意的情况下翻译本文。本翻译内容仅供…...
武汉阳网站建设市场/域名检测查询
虚拟化是什么? 虚拟化是一种将计算机资源(如处理器、内存、存储等)抽象化和隔离的技术,以实现多个虚拟环境共享物理计算机资源的目的。通俗地说,虚拟化就是将一个物理主机分割成多个虚拟的、相互独立的虚拟机…...
wordpress不好/seo如何挖掘关键词
Android 内存溢出和内存泄漏的问题在面试中,经常有面试官会问“你知道什么是内存溢出?什么是内存泄漏?怎么避免?”通过这篇文章,你可以回答出来了。内存溢出(OOM)是指程序在申请内存时,没有足够的内存空间供…...
如何做凡客网站/沈阳seo关键词
有人说:一个人从1岁活到80岁很平凡,但如果从80岁倒着活,那么一半以上的人都可能不凡。 生活没有捷径,我们踩过的坑都成为了生活的经验,这些经验越早知道,你要走的弯路就会越少。 一、异步执行 实现方式二…...
网站改版的原因/电子商务营销策略
SPI(Service Provider Interface) 是Java提供的一套用来被第三方实现或者扩展的API,它可以用来启用框架扩展和替换组件。 java中实现spi的主要类为 ServiceLoader<S>,一个简单的服务提供商加载工具。 一个服务是一组众所周知的接口&am…...