83 篇用过滤器 Filter 把没带令牌的请求挡在了门外。这一篇(PPT 第 43~53 页)讲"统一拦截"的第二套方案——Spring 自己提供的拦截器 Interceptor:校验逻辑几乎一模一样,但换了一套写法(实现的是 Spring 的接口、靠配置类注册),而且能更精细地决定"拦哪些、不拦哪些"。最后把 Filter 与 Interceptor 摆在一起对比,并用一次本机实测修正 PPT 里那句容易读歪的话。
这一节的位置(PPT 第 43~44 页)
第 43 页是"登录校验"这一节的目录(会话技术 / JWT 令牌 / 过滤器 Filter / 拦截器 Interceptor),第 44 页把拦截器这一节又拆成三块:
快速入门 → 令牌校验 Interceptor → 详解
结构和 83 篇完全对称——因为两套方案要解决的问题是同一个(在请求到达业务代码之前统一校验令牌),所以本篇会把笔墨更多放在"写法上的差别"和"两者到底差在哪"。
什么是拦截器(PPT 第 45 页)
PPT 给出的定义:
概念:是一种动态拦截方法调用的机制,类似于过滤器。Spring 框架中提供的,主要用来动态拦截控制器方法的执行。
作用:拦截请求,在指定的方法调用前后,根据业务需要执行预先设定的代码。
和过滤器放在一起看,差别一眼就出来了:Filter 拦的是请求(Servlet 层面的东西,请求还没进 Spring MVC 就被它经手),Interceptor 拦的是控制器方法(Spring MVC 内部的东西,请求已经到了 Spring MVC、正要调用某个 Controller 方法时被它经手)。
浏览器 ──请求──▶ Interceptor ──▶ Controller(Login / Emp / Dept / Report)
◀──响应── ◀──PPT 第 45 页图上画的正是"请求和响应都从 Interceptor 穿过、后面才是各个 Controller"。
拦截器快速入门(PPT 第 46~47 页)
PPT 第 46 页把入门拆成两半:① 定义拦截器 + ② 注册拦截器。
① 定义拦截器:实现 HandlerInterceptor 接口
@Slf4j
@Component // 交给 Spring 管理——注册时才能把它注入到配置类里
public class DemoInterceptor implements HandlerInterceptor {
//在目标资源方法运行之前运行 —— 返回值:true 放行,false 不放行
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
log.info("preHandle ....");
return true;
}
//在目标资源方法运行之后运行
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
log.info("postHandle ....");
}
//视图渲染完毕后运行(最后执行)
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
log.info("afterCompletion ....");
}
}NOTE
PPT 第 46 页上那段代码里,postHandle 里打印的字符串写成了 "preHandle..."——是页面上的一处笔误;课程工程 DemoInterceptor.java 里已经改成了正确的 "postHandle ....",照抄时以工程代码为准。
② 注册拦截器:配置类实现 WebMvcConfigurer
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private DemoInterceptor demoInterceptor; // 注入要注册的拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(demoInterceptor) // 注册拦截器
.addPathPatterns("/**"); // 拦截所有请求
}
}IMPORTANT
拦截器和过滤器最大的写法差别就在"配置"这一步:
- 过滤器靠一个注解(
@WebFilter)+ 引导类开关(@ServletComponentScan)生效; - 拦截器靠一个配置类(
@Configuration+ 实现WebMvcConfigurer),在addInterceptors方法里registry.addInterceptor(...)手写注册——所以它能配上addPathPatterns(要拦截哪些资源)和excludePathPatterns(不需要拦截哪些资源)两个方法,控制粒度更细。
课程工程里 WebConfig 的注册代码是注释状态(默认启用的是 83 篇的 Filter 方案,注释里就是拦截器方案的注册代码),要做拦截器实验时把 @WebFilter 注释掉、把这段注册代码放开即可。
三个方法分别在什么时候执行
| 方法 | 执行时机 | 说明 |
|---|---|---|
preHandle | 目标资源方法执行前 | 返回 true 放行、false 不放行——返回 false 时,Controller 方法不会执行,postHandle / afterCompletion 也不会执行 |
postHandle | 目标资源方法执行后 | 此时 Controller 已经跑完,但"视图"还没渲染 |
afterCompletion | 视图渲染完毕后执行,最后执行 | 一次请求的收尾(清理资源、记录耗时等) |
TIP
这门课的项目是前后端分离的(Controller 返回 JSON、没有页面视图),所以 postHandle 执行完、"视图渲染"这一步没有实际内容,afterCompletion 紧接着就会执行。三者的顺序仍然是 preHandle →(Controller 方法)→ postHandle → afterCompletion。
必答问答(PPT 第 47 页)
| PPT 的问题 | 答案 |
|---|---|
| 拦截器 Interceptor 的使用步骤? | 定义:实现 HandlerInterceptor 接口,重写三个方法(preHandle、postHandle、afterCompletion);配置:定义一个配置类实现 WebMvcConfigurer 接口,在 addInterceptors 里注册拦截器(/**) |
登录校验拦截器(PPT 第 48~49 页)
第 48 页的目录页把这一节的分段标出来(快速入门 / 令牌校验 Interceptor / 详解);第 49 页给出的六步流程和第 37 页 Filter 的一模一样:
- 获取请求 url;
- 判断请求 url 中是否包含
login,如果包含,说明是登录操作,放行; - 获取请求头中的令牌(token);
- 判断令牌是否存在,如果不存在,响应 401;
- 解析 token,如果解析失败,响应 401;
- 放行。
请求 ──▶ 获取请求路径 ──▶ 路径里包含 login ? ──是──▶ 放行
│否
▼
获取请求头 token ──▶ 有 token ? ──否──▶ 响应 401
│是
▼
解析 token ──▶ 解析成功? ──否──▶ 响应 401
│是
▼
放行流程一样,代码也几乎一样,写在 preHandle 里(课程工程 com.itheima.interceptor.TokenInterceptor):
@Slf4j
@Component
public class TokenInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// //1. 获取到请求路径
// String requestURI = request.getRequestURI();
//
// //2. 判断是否是登录请求——这一步改用注册时的 excludePathPatterns("/login") 实现
// if (requestURI.contains("/login")) {
// log.info("登录请求, 放行");
// return true;
// }
//3. 获取请求头中的 token
String token = request.getHeader("token");
//4. 判断 token 是否存在,如果不存在,说明用户没有登录,返回 401
if (token == null || token.isEmpty()) {
log.info("令牌为空, 响应401");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); // 401
return false; // 返回 false:不放行
}
//5. 如果 token 存在,校验令牌,如果校验失败 -> 返回 401
try {
JwtUtils.parseToken(token);
} catch (Exception e) {
log.info("令牌非法, 响应401");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); // 401
return false; // 返回 false:不放行
}
//6. 校验通过,放行
log.info("令牌合法, 放行");
return true; // 返回 true:放行
}
}配套的注册代码(写在 WebConfig 里,课程工程中注释着):
@Autowired
private TokenInterceptor tokenInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(tokenInterceptor)
.addPathPatterns("/**") // 拦截所有请求
.excludePathPatterns("/login"); // 不拦截登录请求
}和 Filter 版对照,两个关键差别:
IMPORTANT
差别一:放行/拦截的表达方式不同。 过滤器用"调不调用 chain.doFilter(...)"来表达(不调用就断在过滤器里);拦截器统一用 preHandle 的返回值表达——return true 放行、return false 拦截(返回 false 时 Controller 方法根本不会被调用)。
差别二:"登录请求例外"的实现方式不同。 Filter 版是在代码里判断 requestURI.contains("/login");Interceptor 版把这行判断删掉(工程代码里注释着),改用注册时的 excludePathPatterns("/login")——把"例外名单"从代码搬到了配置里,要排除多条路径时只需要追加参数,不用写一串 if。
第 50 页的目录页把拦截器这一节的"详解"拆成两块:拦截路径(第 51 页)和执行流程(第 52 页)——本篇先用一次本机实测把校验效果钉死,再分别讲这两块。
本机实测:拦截器方案
把 TokenFilter 的 @WebFilter 注释掉、在 WebConfig 里注册 TokenInterceptor(addPathPatterns("/**") + excludePathPatterns("/login")),再发一遍 83 篇里那几种请求:
TIP
本机实测:拦截器方案下的三种请求
不带 token 访问 /emps → HTTP 401
带合法 token 访问 /emps → HTTP 200
POST /login → 放行(excludePathPatterns 生效,能正常登录)结果和 Filter 方案完全一致:没令牌 401、令牌合法 200;唯一的差别是 /login 这次不是靠代码里的路径判断放行的,而是它在注册时就被排除掉了。也就是说,两种方案都能把"登录校验"这件事办成,业务代码一个字都不用改——只是一个发生在 Spring MVC 之外、一个发生在 Spring MVC 之内。
(本机连的是 MySQL 的 tlias 库、用户名 root;password 换成你自己 MySQL 的密码。)
拦截器的拦截路径(PPT 第 51 页)
PPT 第 51 页说,拦截器可以根据需求配置"需要拦截哪些资源"和"不需要拦截哪些资源",路径的写法有四种:
| 拦截路径 | 含义 | 举例 |
|---|---|---|
/* | 一级路径 | 能匹配 /depts、/emps、/login,不能匹配 /depts/1 |
/** | 任意级路径 | 能匹配 /depts、/depts/1、/depts/1/2 |
/depts/* | /depts 下的一级路径 | 能匹配 /depts/1,不能匹配 /depts/1/2,也不能匹配 /depts |
/depts/** | /depts 下的任意级路径 | 能匹配 /depts、/depts/1、/depts/1/2,不能匹配 /emps/1 |
几个容易看错的点:
*与**的区别就是"吃一段"还是"吃任意段":/*只吃一级,/**吃到底;所以/depts/*匹配/depts/1却匹配不了/depts/1/2——多出来的层级它管不到。/depts/*连/depts本身都不匹配(PPT 表格里专门写了这一点):*必须对应"一段路径",而/depts后面已经没有段了。- 课程注册用的是
/**(拦截所有请求),因为登录校验要保护的是系统里所有接口,只在excludePathPatterns里把/login单独排除。
图:Tlias 系统的员工管理页面(右上角有"退出登录")——后台这些模块(部门管理、员工管理……)的接口路径就是上表里的 /depts、/emps,登录校验要保护的就是它们
执行流程,以及和过滤器的区别(PPT 第 52 页)
PPT 第 52 页把 Filter、Interceptor、Controller 三者在一次请求里的位置画在了一起:
浏览器 ──请求──▶ Filter 放行前逻辑 ──▶ doFilter() 放行
──▶ 拦截器 preHandle ──▶ Controller 方法执行 ──▶ postHandle
──▶(视图渲染)afterCompletion
──▶ 回到 Filter 放行后逻辑 ──响应──▶ 浏览器也就是说,过滤器在最外层,拦截器在 Spring MVC 里面:请求先过 Filter 的放行前逻辑、被 doFilter() 放行后才进入 Spring MVC;到了 Spring MVC,先执行拦截器 preHandle,通过后调用 Controller 方法,返回时走 postHandle、afterCompletion,最后再回到 Filter 执行放行后逻辑、把响应还给浏览器。
TIP
从这张图还能推断出一件事:两套方案同时开启时,Filter 的判断在前——如果 Filter 直接回了 401、根本没调用 chain.doFilter,请求压根进不了 Spring MVC,拦截器连"看到请求"的机会都没有(这也是 83 篇里说的"断在过滤器里"的效果)。
PPT 同页给出的两点区别:
| 区别 | PPT 的说法 |
|---|---|
| 接口规范不同 | 过滤器需要实现 Filter 接口(Servlet 规范,jakarta.servlet 包),而拦截器需要实现 HandlerInterceptor 接口(Spring MVC,org.springframework.web.servlet 包) |
| 拦截范围不同 | 过滤器 Filter 会拦截所有的资源,而 Interceptor 只会拦截 Spring 环境中的资源 |
第二条是本篇最需要"较真"的一句,因为照字面很容易读成"静态页面不会被拦截器拦"——本机实测的结果并不是这样:
WARNING
本机实测:静态资源也被拦截器拦到了
在工程的 src/main/resources/static/ 下放了一个 test-page.html,用上面那套拦截器(/** + 排除 /login)实测:
不带 token 访问 /test-page.html → HTTP 401 ← 静态页也被拦住了原因:Spring Boot 的静态资源不是"绕开 Spring MVC"直接由容器吐出去的——它交给 Spring MVC 的 ResourceHttpRequestHandler 处理,所以照样要走 HandlerMapping(找不到 @RequestMapping 就落到这个资源处理器上)→ 一样经过拦截器。
所以 PPT 那句"只拦截 Spring 环境中的资源"的现实含义应该这样理解:请求只要进了 DispatcherServlet,就会被拦截器拦到(静态资源、接口都在内);真正拦不到的是"根本不进 DispatcherServlet 的请求"——比如另一个 Servlet 自己映射的路径、由 web 容器直接处理的资源。
实际写代码时别指望"把页面/接口换个位置就能躲开拦截器":只要它还是 Spring Boot 里被 Spring MVC 接管的资源,拦截器就拦得到;反过来,也正因为静态资源会被拦,拦截路径里的排除名单(excludePathPatterns)才需要认真写——比如把登录相关的接口、不需要登录的页面路径排除掉,否则用户可能连页面都打不开。
必答问答(PPT 第 53 页)
| PPT 的问题 | 答案 |
|---|---|
拦截器中的拦截路径 /* 与 /** 的区别是什么? | /*:只能拦截一级路径,如 /depts、/emps;/**:拦截任意级路径,如 /depts、/depts/1 |
| 过滤器与拦截器的区别? | 接口规范不同:过滤器实现 Filter(Servlet 规范),拦截器实现 HandlerInterceptor(Spring MVC);拦截范围不同:过滤器拦所有资源,拦截器拦 Spring 环境中的资源——本机实测:只要请求进了 DispatcherServlet(包括静态资源)就会被拦,拦不到的是不进 DispatcherServlet 的请求 |
小结
| 问题 | 答案 |
|---|---|
| 拦截器是什么? | Spring 提供的动态拦截方法调用的机制,类似过滤器;主要用来动态拦截控制器方法的执行(在方法调用前后执行预设代码) |
| 怎么定义拦截器? | 实现 HandlerInterceptor 接口,重写 preHandle / postHandle / afterCompletion;加 @Component 交给 Spring 管理 |
| 怎么注册? | 写一个配置类实现 WebMvcConfigurer,在 addInterceptors(InterceptorRegistry registry) 里 registry.addInterceptor(...).addPathPatterns("/**") |
| 三个方法的时机? | preHandle:目标资源方法执行前(返回 true 放行、false 不放行);postHandle:方法执行后;afterCompletion:视图渲染完毕后执行,最后执行 |
| 放行怎么表达? | 统一用 preHandle 的返回值——return true 放行、return false 拦截(返回 false 时 Controller 与后两个方法都不执行) |
| "登录请求例外"怎么写? | 注册时 addPathPatterns("/**") + excludePathPatterns("/login"),不用在代码里判断路径 |
| 拦截路径怎么配? | /* 一级路径;/** 任意级路径;/depts/* /depts 下的一级路径(不含 /depts 本身);/depts/** /depts 下的任意级路径 |
| 与 Filter 的两点区别? | 接口规范不同(Filter vs HandlerInterceptor);拦截范围不同(过滤器拦所有资源;拦截器拦 Spring 环境中的资源——实测:进 DispatcherServlet 的静态资源也会被拦) |
| 本机实测的结果? | 无 token → 401;带合法 token → 200;POST /login → 放行(excludePathPatterns 生效);/test-page.html 不带 token → 401(静态资源也被拦) |
相关
练习题
一、知识回顾(读完直接做下面的实践题)
- 拦截器的定位:一种动态拦截方法调用的机制,类似于过滤器;Spring 框架中提供的,主要用来动态拦截控制器方法的执行
- 拦截器的作用:拦截请求,在指定的方法调用前后,根据业务需要执行预先设定的代码
- 快速入门两步:定义——实现
HandlerInterceptor接口并重写三个方法;注册——写一个配置类实现WebMvcConfigurer,在addInterceptors里注册 - 三个方法的执行时机:
preHandle——目标资源方法执行前(返回true放行、false不放行);postHandle——目标资源方法执行后;afterCompletion——视图渲染完毕后执行,最后执行 - "放行/拦截"的表达:拦截器统一用
preHandle的返回值——return true放行、return false不放行(返回false时 Controller 方法和后面两个方法都不会执行) - 注册时的两个方法:
addPathPatterns(...)配"要拦截哪些资源"、excludePathPatterns(...)配"不需要拦截哪些资源"(课程的登录校验就是/**+ 排除/login) - 拦截路径的四种写法:
/*一级路径(/depts、/emps、/login,不含/depts/1);/**任意级路径;/depts/*/depts下的一级路径(/depts/1,不含/depts/1/2,也不含/depts本身);/depts/**/depts下的任意级路径 - Filter 与 Interceptor 的两点区别:接口规范不同(
FiltervsHandlerInterceptor);拦截范围不同(过滤器拦所有资源,拦截器拦 Spring 环境中的资源) - 拦截范围的实测修正:拦截器方案下不带 token 访问
resources/static/test-page.html同样返回 HTTP 401——静态资源由 Spring MVC 的ResourceHttpRequestHandler处理,照样过拦截器;真正拦不到的是不进 DispatcherServlet 的请求 - 本机实测:注册
TokenInterceptor(/**+ 排除/login)后——无 token 访问/emps→ 401;带合法 token → 200;POST /login→ 放行(excludePathPatterns生效)
二、裸写题
- 提示(先自己想,实在想不出再点开)
一级 · 思路:分两段写——一段是拦截器本身(三个方法,一个管"放不放行"、两个管"前后记一笔"),一段是配置类把它注册进去 二级 · 方法:拦截器实现
HandlerInterceptor、加@Component;配置类加@Configuration并实现WebMvcConfigurer,在addInterceptors(InterceptorRegistry registry)里registry.addInterceptor(...).addPathPatterns("/**")三级 · 骨架:java@Component public class DemoInterceptor implements ____ { public boolean ____(HttpServletRequest req, HttpServletResponse resp, Object handler) { return ____; } public void ____(HttpServletRequest req, HttpServletResponse resp, Object handler, ModelAndView mv) { …… } public void ____(HttpServletRequest req, HttpServletResponse resp, Object handler, Exception ex) { …… } } @Configuration public class WebConfig implements ____ { @Autowired private DemoInterceptor demoInterceptor; @Override public void ____(____ registry) { registry.____(demoInterceptor).____("/**"); } }- 参考答案(做完再点开)
java@Slf4j @Component public class DemoInterceptor implements HandlerInterceptor { //在目标资源方法运行之前运行 —— 返回值:true 放行,false 不放行 @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { log.info("preHandle ...."); return true; } //在目标资源方法运行之后运行 @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { log.info("postHandle ...."); } //视图渲染完毕后运行(最后执行) @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { log.info("afterCompletion ...."); } }java@Configuration public class WebConfig implements WebMvcConfigurer { @Autowired private DemoInterceptor demoInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(demoInterceptor).addPathPatterns("/**"); } }自查:① 一次请求的日志顺序是
preHandle→(Controller 自己的日志)→postHandle→afterCompletion,说明三个方法正好夹在 Controller 前后和请求收尾;② 把preHandle的return true改成return false,接口就再也拿不到数据了,而且postHandle/afterCompletion都不会打印——这就是"不放行";③ 拦截器类忘了加@Component时,配置类里的@Autowired会注入失败(启动直接报错),因为注册的对象得先由 Spring 管理。- 提示(先自己想,实在想不出再点开)
一级 · 思路:校验的六步和过滤器版本一样,变的只有两处——"不放行"不再用"不调用放行",而是"返回一个布尔值";登录例外不在代码里判路径,而是写进注册时的排除名单 二级 · 方法:
preHandle返回boolean(return true放行、return false不放行);类上加@Component(配置类里要注入它);401 用HttpServletResponse.SC_UNAUTHORIZED;解析令牌用JwtUtils.parseToken(token)并用try/catch包住;注册时addPathPatterns("/**")+excludePathPatterns("/login")三级 · 骨架:java@Component public class TokenInterceptor implements ____ { public boolean ____(HttpServletRequest request, HttpServletResponse response, Object handler) { String token = request.____("token"); if (token == null || token.isEmpty()) { response.setStatus(____); return ____; } try { ____.____(token); } catch (Exception e) { response.setStatus(____); return ____; } return ____; } } // 注册:registry.addInterceptor(tokenInterceptor).addPathPatterns("____").excludePathPatterns("____");- 参考答案(做完再点开)
java@Slf4j @Component public class TokenInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //1. 获取请求头中的 token String token = request.getHeader("token"); //2. 判断 token 是否存在,如果不存在,说明用户没有登录,返回 401 if (token == null || token.isEmpty()) { log.info("令牌为空, 响应401"); response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); // 401 return false; // 不放行 } //3. 如果 token 存在,校验令牌,如果校验失败 -> 返回 401 try { JwtUtils.parseToken(token); } catch (Exception e) { log.info("令牌非法, 响应401"); response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); // 401 return false; // 不放行 } //4. 校验通过,放行 log.info("令牌合法, 放行"); return true; // 放行 } }注册(配置类里):
java@Configuration public class WebConfig implements WebMvcConfigurer { @Autowired private TokenInterceptor tokenInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(tokenInterceptor) .addPathPatterns("/**") // 拦截所有请求 .excludePathPatterns("/login"); // 不拦截登录请求 } }自查:① 两个"拦下"分支都是
return false,通过时才是return true;② 注册里必须有excludePathPatterns("/login"),否则登录请求会被这个拦截器挡在门外、用户永远登不上(本机实测里/login能正常返回登录结果,就是这一行在起作用);③ 本机实测的三种结果:无 token → 401、带合法 token → 200、POST /login→ 放行;④ 和过滤器版本对照一下:拦截器版本的代码里已经没有判断请求路径那两步了。- 提示(先自己想,实在想不出再点开)
一级 · 思路:
*与**的区别就是"吃一段"还是"吃任意段";前面带上具体目录,就表示"只在这个目录下" 二级 · 方法:/*一级路径;/**任意级路径;/depts/*是/depts下的一级路径(/depts本身不算);/depts/**是/depts下的任意级路径(包含/depts本身) 三级 · 骨架:①/*→ 能匹配____,不能匹配____;②/**→ ____;③/depts/*→ ____;④/depts/**→ ____;Ⅰ 用____;Ⅱ 用____- 参考答案(做完再点开)
①
/*(一级路径):能匹配/depts;不能匹配/depts/1、/depts/1/2、/emps/1。 ②/**(任意级路径):/depts、/depts/1、/depts/1/2、/emps/1都能匹配(任意层级、任意路径)。 ③/depts/*(/depts下的一级路径):能匹配/depts/1;不能匹配/depts/1/2(层级太深),也不能匹配/depts(后面没跟段,*顶不上);/emps/1更不在它的管辖范围内。 ④/depts/**(/depts下的任意级路径):能匹配/depts、/depts/1、/depts/1/2;不能匹配/emps/1(不在/depts下)。 Ⅰ 拦截/depts下的一级路径:/depts/*。 Ⅱ 拦截/depts本身和它下面的任意级:/depts/**。 顺带回答课程的两个选择:登录校验注册的是/**——因为它要保护系统里所有接口(全都在/depts、/emps这些不同前缀下),只把/login放进排除名单。- 提示(先自己想,实在想不出再点开)
一级 · 思路:区别从"实现哪个接口"和"拦的范围"两头说;第 ② 问先回忆一件事——Spring Boot 里的静态资源到底是谁在处理? 二级 · 方法:接口规范——
Filter(Servlet 规范)与HandlerInterceptor(Spring MVC);范围——过滤器靠@WebFilter的路径配到/*就拦所有;Spring Boot 的静态资源由 Spring MVC 的ResourceHttpRequestHandler处理,所以要过 HandlerMapping → 拦截器;真正躲过拦截器的是"根本没进 DispatcherServlet"的请求(例如另一个 Servlet 自己映射的路径、容器直接处理的资源) 三级 · 骨架:① 接口规范不同:;拦截范围不同:;② 这句话____(对/不对),本机实测不带 token 访问test-page.html返回的是____,因为____;③ 拦不到的是____- 参考答案(做完再点开)
① 接口规范不同:过滤器实现
Filter接口(Servlet 规范,jakarta.servlet包);拦截器实现HandlerInterceptor接口(Spring MVC,org.springframework.web.servlet包)。拦截范围不同:过滤器会拦截所有资源;拦截器只拦截 Spring 环境中的资源。 ② 这句话不能按字面理解(不能推出"静态页面不会被拦")。本机实测:拦截器方案下不带 token 访问/test-page.html返回 HTTP 401——静态页照样被拦住了。原因:Spring Boot 的静态资源是交给 Spring MVC 的ResourceHttpRequestHandler处理的,请求要经过 HandlerMapping(没有匹配的@RequestMapping就落到这个资源处理器)→ 一样会经过拦截器。 ③ 所以"只拦截 Spring 环境中的资源"的现实含义是"请求只要进了 DispatcherServlet 就会被拦到";真正拦不到的是不进 DispatcherServlet 的请求——比如另一个 Servlet 自己映射的路径、由 web 容器直接处理的资源。实际开发中别指望靠"换路径"躲开拦截器,反而要注意把不需要登录的接口、静态资源这类路径写进排除名单(excludePathPatterns)。
三、综合题
- 让原来的过滤器失效(把过滤器类上那个让它生效的注解注释掉),确认工程里没有过滤器在拦请求;
- 在配置类里注入拦截器并注册:拦截所有请求、排除登录请求;
- 启动工程,发三种请求并记录状态码:不带 token 访问
/emps、带合法 token 访问/emps、POST /login; - 在
src/main/resources/static/下放一个test-page.html(随便写几行字),不带 token 访问它,记录状态码; - 把第 4 步的状态码和 PPT 第 52 页"拦截器只会拦截 Spring 环境中的资源"这句话摆在一起,解释为什么会是这个结果;
- 收尾回答两个问题。
(练习文件
test_84_登录校验拦截器.java的"综合题"一段里按这 6 步给了写作区。)回答:① 同一个"登录校验",Filter 方案和 Interceptor 方案各方便在哪?(各说一条)② 如果有一个请求既没进 DispatcherServlet、也不在过滤器的拦截路径里,它会被两套方案拦住吗?
涉及知识点
知识点 在这里的应用 拦截器概念与作用(PPT 45) 第 1、2 步——用拦截器替换过滤器来做同一件事 HandlerInterceptor三方法(PPT 46~47)第 2 步——校验写在 preHandle,用返回值表达放行WebMvcConfigurer注册(PPT 46~47)第 2、3 步—— addPathPatterns/excludePathPatterns拦截器的六步流程(PPT 49) 第 2、3 步——无 token 401、令牌非法 401、通过放行 拦截路径(PPT 51) 第 2、3 步—— /**拦所有 + 排除/login执行流程与两点区别(PPT 52) 第 4、5 步——静态资源的实测与解释 - 提示(先自己想,实在想不出再点开)
一级 · 思路:先把过滤器"关掉"避免两套方案同时生效(否则 401 可能来自过滤器,实验就说不清了),再用拦截器把同样的三种请求跑一遍,最后加一个静态页做"拦截范围"的对照 二级 · 方法:过滤器失效靠注释
@WebFilter;拦截器注册在WebMvcConfigurer的addInterceptors里;静态页放在src/main/resources/static/下、访问路径是/test-page.html;状态码用 401 / 200 记 三级 · 骨架:① 注释掉@____(urlPatterns = "/*");②registry.addInterceptor(tokenInterceptor).addPathPatterns("____").excludePathPatterns("____");③POST http://localhost:8080/____,其余两个带/不带请求头____: <token>;④ 访问http://localhost:8080/____.html- 参考答案(做完再点开)
1~2. 过滤器失效:把
TokenFilter上的@WebFilter(urlPatterns = "/*")注释掉;注册拦截器:配置类里registry.addInterceptor(tokenInterceptor).addPathPatterns("/**").excludePathPatterns("/login")(拦截器代码见题目 2-2 的答案)。 3. 本机实测:text不带 token 访问 /emps → HTTP 401 带合法 token 访问 /emps → HTTP 200 POST /login → 放行(excludePathPatterns 生效,能正常登录)和 83 篇的 Filter 方案结果完全一致——说明两套方案在"登录校验"这件事上是等价可替换的。 4~5. 本机实测:不带 token 访问
/test-page.html→ HTTP 401,静态页也被拦住了。PPT 说"拦截器只拦截 Spring 环境中的资源",而 Spring Boot 的静态资源由 Spring MVC 的ResourceHttpRequestHandler处理(请求会经过 HandlerMapping),所以它在 Spring 的环境中、照样被拦。这句话不能被读成"静态资源不受拦截器管辖";正确的理解是"请求只要进了 DispatcherServlet 就会被拦,真正拦不到的是不进 DispatcherServlet 的请求"。 6. 两个回答: ① Filter 方案方便在"一网打尽":一个@WebFilter(urlPatterns = "/*")就能管住所有请求(连演示接口/c1、/c2都跑不掉),静态资源、非 Spring 的 Servlet 路径都在它的射程内;Interceptor 方案方便在"精确控制 + 更贴 Spring":addPathPatterns/excludePathPatterns能在配置里写清楚"拦哪些、不拦哪些"(不用在代码里判断路径),而且三个方法能在 Controller 前后、请求收尾各插一段代码(preHandle的参数里还带着handler,能知道这次要执行的是哪个方法)。 ② 不会。两套方案的判断依据都是"请求有没有经过自己":过滤器只拦它配的路径范围内的请求,DispatcherServlet 之外的请求根本不进 Spring MVC,拦截器就更看不到了——这正是二者"拦截范围"差别里"拦不到"那一半的真实含义。
