全局视角浏览器请求 →Filter过滤器Servlet 层→ DispatcherServlet →Interceptor拦截器SpringMVC 层→ Controller返回Controller → Interceptor → Filter → 浏览器Filter:属于 Servlet 规范由 Tomcat 容器创建管理不属于 Spring因为不属于 Spring所以不方便直接使用Autowired 注入 SpringBean以下代码演示了如何创建一个过滤器来拦截所有请求并打印日志import javax.servlet.*; import javax.servlet.annotation.WebFilter; import java.io.IOException; WebFilter(/*) // 匹配所有请求注意Spring Boot 内置 Tomcat 还要在启动类加 ServletComponentScan否则这个注解会被忽略、过滤器不生效 public class LoggingFilter implements Filter { Override public void init(FilterConfig filterConfig) throws ServletException { System.out.println(过滤器初始化); } Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { System.out.println(请求被拦截 - 处理前); chain.doFilter(request, response); // 放行请求刚需不然所有请求都断在这里 System.out.println(响应被拦截 - 处理后); } Override public void destroy() { System.out.println(过滤器销毁); } }Filter 的注解方式通过 WebFilter 注解/*表示匹配所有请求。Filter 匹配/*时所有请求都会进来包括 CSS、js 和静态页面等。Interceptor 会不会被拦到取决于请求路径有没有命中 addPathPatterns命中就会进来静态资源命中了也一样会进来。之所以静态资源没有被拦住是因为 preHandle 里手动放行了if (!(handler instanceof HandlerMethod)) { // 不是 Controller 的方法静态资源的 handler 是 ResourceHttpRequestHandler直接放行 return true; }也就是说不是拦截器不拦静态资源而是代码里判断后把非 Controller 的请求放行了。Interceptor属于 SpringMVC 框架执行位于 DispatcherServlet 之后Controller 之前。会拦截命中 addPathPatterns 的请求静态资源不是自动不拦而是靠上面那行判断放行支持Autowired 注入 Bean在苍穹外卖中使用 Interceptor 进行登录校验操作因为它属于 Spring 组件可以直接注入 JwtProperties 这类配置 Bean。注意JwtUtil 是纯静态工具类方法全是 static不在 Spring 容器里注入不了也不需要注入直接JwtUtil.parseJWT(...)用类名调即可。苍穹外卖中的 Interceptor结合 JWT文件角色sky-server/.../interceptor/JwtTokenAdminInterceptor.java管理端拦截器拦/admin/**sky-server/.../interceptor/JwtTokenUserInterceptor.java用户端拦截器拦/user/**sky-server/.../config/WebMvcConfiguration.java注册两个拦截器 配置放行名单sky-common/.../utils/JwtUtil.java签发 / 解析 JWTsky-common/.../properties/JwtProperties.java读sky.jwt.*配置两套密钥/过期时间/请求头名sky-common/.../constant/JwtClaimsConstant.java载荷 key 常量empId、userIdsky-common/.../context/BaseContext.javaThreadLocalLong在拦截器里存、在 Service 里取sky-server/.../controller/admin/EmployeeController.java管理端登录签发 tokensky-server/.../controller/user/UserController.java用户端微信登录签发 tokensky-server/src/main/resources/application.ymlsky.jwt两套配置JwtTokenAdminInterceptor.java/** * jwt令牌校验的拦截器 */ Component Slf4j public class JwtTokenAdminInterceptor implements HandlerInterceptor { Autowired private JwtProperties jwtProperties; public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //判断当前拦截到的是Controller的方法还是其他资源 if (!(handler instanceof HandlerMethod)) { //当前拦截到的不是动态方法直接放行 return true; } //1、从请求头中获取令牌 String token request.getHeader(jwtProperties.getAdminTokenName()); //2、校验令牌 try { log.info(jwt校验:{}, token); Claims claims JwtUtil.parseJWT(jwtProperties.getAdminSecretKey(), token); Long empId Long.valueOf(claims.get(JwtClaimsConstant.EMP_ID).toString()); log.info(当前员工id, empId); BaseContext.setCurrentId(empId); //3、通过放行 return true; } catch (Exception ex) { //4、不通过响应401状态码 response.setStatus(401); return false; } } }这是 JWT 令牌校验的拦截器上面是管理端拦截器拦/admin/**拿到 token -- 校验 token 中的载荷claims其中包含存储的信息这里是员工 id-- 校验通过拿到 empId -- 放入 BaseContext是自定义 SpringMVC 拦截器专门用来校验管理员端请求的 JWT 令牌。在请求进入 Controller 之前执行校验 token 合法性解析出管理员 id 存入BaseContext校验失败直接拦截请求返回 401。JwtUtil.parseJWT()方法的调用是校验的核心是定义在JwtUtil.java中的public static Claims parseJWT(String secretKey, String token) { // 得到DefaultJwtParser Claims claims Jwts.parser() // 设置签名的秘钥 .setSigningKey(secretKey.getBytes(StandardCharsets.UTF_8)) // 设置需要解析的jwt .parseClaimsJws(token).getBody(); return claims; }Jwts.parser()获取 JWT 解析器.setSigningKey()告诉解析器验证签名要用哪个密钥.parseClaimsJws(token)拆分 token 三段使用 secretKey 校验签名校验 exp 过期时间✅ 全部通过返回 Jws 对象调用.getBody()拿到载荷 Claims❌ 任意失败直接抛出异常SignatureException/ExpiredJwtException等拦截器外层的try-catch捕获这个异常返回 401WebMvcConfiguration.java关键代码截取Configuration Slf4j public class WebMvcConfiguration extends WebMvcConfigurationSupport { Autowired private JwtTokenAdminInterceptor jwtTokenAdminInterceptor; Autowired private JwtTokenUserInterceptor jwtTokenUserInterceptor; Override protected void addInterceptors(InterceptorRegistry registry) { log.info(开始注册自定义拦截器...); // 1、管理员端拦截器jwtTokenAdminInterceptor registry.addInterceptor(jwtTokenAdminInterceptor) .addPathPatterns(/admin/**)// 拦截匹配 /admin/** 的所有路径 .excludePathPatterns(/admin/employee/login); // 排除登录接口不拦截 // 2、用户端拦截器jwtTokenUserInterceptor registry.addInterceptor(jwtTokenUserInterceptor) .addPathPatterns(/user/**)// 拦截匹配 /user/** 的所有路径 .excludePathPatterns(/user/user/login)// 排除用户登录接口 .excludePathPatterns(/user/shop/status);// 排除店铺状态查询接口 }单纯是上述的管理端和用户端拦截器的注册JwtUtil.javapublic class JwtUtil { /** * 生成jwt * 使用Hs256算法, 私匙使用固定秘钥 * * param secretKey jwt秘钥 * param ttlMillis jwt过期时间(毫秒) * param claims 设置的信息 * return */ public static String createJWT(String secretKey, long ttlMillis, MapString, Object claims) { // 指定签名的时候使用的签名算法也就是header那部分 SignatureAlgorithm signatureAlgorithm SignatureAlgorithm.HS256; // 生成JWT的时间 long expMillis System.currentTimeMillis() ttlMillis; Date exp new Date(expMillis); // 设置jwt的body JwtBuilder builder Jwts.builder() // 如果有私有声明一定要先设置这个自己创建的私有的声明这个是给builder的claim赋值一旦写在标准的声明赋值之后就是覆盖了那些标准的声明的 .setClaims(claims) // 设置签名使用的签名算法和签名使用的秘钥 .signWith(signatureAlgorithm, secretKey.getBytes(StandardCharsets.UTF_8)) // 设置过期时间 .setExpiration(exp); return builder.compact(); } /** * Token解密 * * param secretKey jwt秘钥 此秘钥一定要保留好在服务端, 不能暴露出去, 否则sign就可以被伪造, 如果对接多个客户端建议改造成多个 * param token 加密后的token * return */ public static Claims parseJWT(String secretKey, String token) { // 得到DefaultJwtParser Claims claims Jwts.parser() // 设置签名的秘钥 .setSigningKey(secretKey.getBytes(StandardCharsets.UTF_8)) // 设置需要解析的jwt .parseClaimsJws(token).getBody(); return claims; } }JwtUtil 是一个JWT 工具类封装了 JWT 令牌的生成、解析、校验方法专门用来创建 token 和解析 token 中的用户信息在 JWT 拦截器中调用。什么时候该用 Filter 而不是 InterceptorFilter 更底层、更通用跨域处理、字符编码、请求体包装比如 XSS 过滤、Body 重复读、全局访问日志——这些要在进入 Spring MVC 之前做就用 Filter。而要拿到 Controller 方法/注解信息的比如AutoFill、登录校验要知道 handler就用 Interceptor 或 AOP。Intercepter 与 JWT 拦截器校验1第一次请求登录POST /admin/employee/login前端提交账号密码访问登录接口请求进入 Tomcat经过 Filter到达 DispatcherServlet匹配拦截器配置excludePathPatterns(/admin/employee/login)→跳过 JwtTokenAdminInterceptor进入 EmployeeController 的 login 方法Controller 校验账号密码是否正确如果账号密码正确调用JwtUtil.generateToken()生成 JWT 令牌把 JWT 字符串返回给前端前端拿到 token保存在浏览器本地存储localStorage这一步只生成 JWT没有 JWT 校验。还没 token自然不需要校验。2第二次请求登录之后访问业务接口例如 /admin/dish/list前端发请求请求头带上token: JWT字符串这个时候拦截器才会触发 JWT 校验浏览器发起请求GET /admin/dish/listheader 携带 token请求经过 Filter到达 DispatcherServlet匹配拦截器规则/admin/**进入 JwtTokenAdminInterceptor 的 preHandle ()preHandle 里面获取请求头的 token调用JwtUtil.parseJWT(密钥, token)jjwt 底层校验签名、过期时间校验成功取出 empId 放入 BaseContextreturn true 放行进入 DishController校验失败catch 捕获异常设置 401 状态码 return false请求终止不会进到 ControllerController 执行业务可从 BaseContext 拿到当前登录员工 id用于公共字段自动填充 createUser/updateUser请求结束执行拦截器afterCompletion()清理 BaseContext 中的 ThreadLocal