Spring Boot项目必备三大组件:跨域、JWT拦截器、统一响应

📅 2026/7/23 15:59:06
Spring Boot项目必备三大组件:跨域、JWT拦截器、统一响应
一、CorsConfigConfiguration public class CorsConfig { Bean public CorsFilter corsFilter() { CorsConfiguration config new CorsConfiguration(); config.addAllowedOrigin(http://localhost:5173); config.addAllowedHeader(*); config.addAllowedMethod(*); config.setAllowCredentials(true); config.setMaxAge(3600L); UrlBasedCorsConfigurationSource source new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration(/**, config); return new CorsFilter(source); } }解释一下这个代码Configuration告诉Spring这是一个配置类启动时就会加载。CorsFilter这是一个过滤器比拦截器还靠前在请求还没到Spring MVC核心之前就执行专门用来在响应头里添加Access-Control-Allow-Origin等字段。addAllowedOrigin(http://localhost:5173)只允许这个地址的网页访问我的后端。如果你写*那就是允许所有但是这样可能会被恶意访问。addAllowedHeader(*)允许前端请求携带任何请求头比如Authorization、Content-Type。addAllowedMethod(*)允许所有HTTP方法GET、POST、PUT、DELETE等。setAllowCredentials(true)允许前端发请求时带上Cookie。这是个大坑点一旦设为trueaddAllowedOrigin就不能用*必须指定具体域名否则浏览器直接报错。setMaxAge(3600L)预检请求OPTIONS的缓存时间单位秒。前端在发“复杂请求”如带自定义头的JSON请求之前会先发一个OPTIONS探路设置3600秒后同一请求1小时内不再发预检性能提升明显。没有它你用Vue的axios请求后端接口浏览器控制台直接报红No Access-Control-Allow-Origin header is present。有了它后端默默给响应头加上了白名单浏览器放行联调顺利。面试高频追问问答什么是跨域为什么会有跨域浏览器的“同源策略”规定协议、域名、端口必须完全一致才能自由访问。前后端端口不一样5173 vs 8080所以被拦截。这是浏览器安全机制不是后端的问题。除了CorsFilter还有哪些方式解决跨域①CrossOrigin注解加在Controller或方法上②实现WebMvcConfigurer重写addCorsMappings③Nginx反向代理前端请求同源地址由Nginx转发到后端。我推荐用CorsFilter因为它全局生效且优先级高。setAllowCredentials(true)和addAllowedOrigin(*)为什么不能同时用因为浏览器规范强制要求如果允许携带凭证Cookie那么Origin必须明确指定不能用通配符。否则浏览器觉得不安全直接拒绝。这是面试必杀坑一定要说出来。生产环境怎么配绝对不能写*必须写具体的前端域名如https://www.myapp.com。如果有多环境建议把域名抽到配置文件用Value注入做到环境隔离。有什么改进建议多源支持用config.setAllowedOriginPatterns()Spring Boot 2.4替代addAllowedOrigin支持*通配符但又能兼容allowCredentialstrue更灵活。放行特定路径如果只需要给/api/**开跨域别写/**缩小范围。二、JwtInterceptorComponent public class JwtInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String uri request.getRequestURI(); if (/admin/login.equals(uri)) { return true; } String authHeader request.getHeader(Authorization); if (authHeader null || !authHeader.startsWith(Bearer )) { response.setStatus(401); response.setContentType(application/json;charsetUTF-8); response.getWriter().write({\code\:401,\message\:\Token缺失或格式错误\}); return false; } String token authHeader.substring(7); try { JwtUtil.parseToken(token); return true; } catch (Exception e) { response.setStatus(401); response.setContentType(application/json;charsetUTF-8); response.getWriter().write({\code\:401,\message\:\Token无效或已过期\}); return false; } } }实现HandlerInterceptor这是Spring MVC的拦截器在请求进入Controller之前执行是鉴权的绝佳时机。白名单放行如果请求是/admin/login直接放行登录还没Token呢。提取Authorization头前端必须把Token放在Authorization: Bearer xxx里这是全球通用的JWT传输标准。校验如果头没有或格式不对直接返回401并手动写一个JSON错误信息再也不往下执行了。解析Token调用工具类JwtUtil.parseToken如果抛异常过期、篡改同样返回401。在项目里它是整个系统的安全闸口。除了登录所有接口比如查询订单、修改个人信息都必须经过它。一旦Token无效请求连Controller的门都摸不到极大节省了服务器资源不用再查数据库验证身份了。问答拦截器(Interceptor)和过滤器(Filter)有什么区别Filter是Servlet规范更底层在请求进入DispatcherServlet之前执行依赖Web容器Interceptor是Spring提供的在DispatcherServlet之后、Controller之前执行能拿到Spring容器里的Bean比如JwtUtil这是最大区别。你的白名单只写了/admin/login如果前端请求/admin/login/带斜杠或/admin/login?xxx会怎样会被拦截因为equals是精确匹配。改进用request.getServletPath()或Spring的AntPathMatcher做模糊匹配或者把白名单放到配置里支持/admin/**通配。为什么不用ControllerAdvice统一处理Token异常而是直接在拦截器里写response.getWriter().write()因为拦截器里抛出的异常默认不会进入全局异常处理器。手动写流是“应急”做法但不规范。更好做法是抛出自定义异常如TokenInvalidException然后在ControllerAdvice中统一捕获并返回Result.error这样错误格式全局一致。Token验证通过后怎么让后续接口知道当前是哪个用户在操作解析Token后把用户ID放入request.setAttribute(userId, userId)或者存入ThreadLocal推荐这样Service层能随时拿到当前用户信息避免重复解析Token。Token过期了你们怎么处理需要刷新机制吗可以配合Redis存刷新令牌Refresh Token过期后返回特定状态码如401前端用刷新令牌换新Token。简单点的话直接跳转登录页。三、Resultpublic class ResultT { private String code; private T data; private String msg; public static T ResultT success() { ... } public static T ResultT success(T data) { ... } public static T ResultT error(String msg) { ... } public static T ResultT error(String code, String msg) { ... } // getter/setter }泛型Tdata字段可以是任何类型比如ResultUser、ResultListOrder编译期就确定类型不用强制转换。静态工厂方法提供success()、success(data)、error(msg)、error(code, msg)调用方便。核心字段code状态码200成功500系统错误401未授权等。msg人类可读的提示信息。data业务数据查询结果。项目中没有它你的Controller可能会返回查询成功 → 直接返回User对象查询失败 → 返回Map带error字段删除成功 → 返回success字符串前端得写无数个if判断解析不同类型。而统一成Result后前端只需要判断result.code 200然后取result.data其他情况统一走错误提示。问答为什么要统一封装直接返回对象不好吗①前后端需要固定契约方便前端全局拦截器处理②包含code和msg可以把业务异常也以正常JSON返回而不是抛出500白页③后期如果需要加timestamp、path等字段只要改这个类所有接口自动生效。这里的T有什么好处类型安全。如果data是Object取出来还得强转容易强转异常。用泛型后ResultUser直接getData()就是User不用手动(User)。状态码用String还是int用String更灵活可以支持A001这种业务码但项目中如果只用数字建议用Integer或枚举比如ResultCode.SUCCESS.getCode()防止手残把200写成200 带空格。success()和success(T data)为什么不合并成一个方法重载让调用意图更清晰。如果合并传null时无法区分是“没数据”还是“业务数据就是null”分开写语义明确。如果我要返回分页数据含total怎么封装可以再写一个PageResultT继承Result或者直接在data里放PageInfoT把total、pageNum、list都塞进data的字段里。四、三者如何协同构成项目骨架组件作用时机产出CorsConfig请求刚到达服务器还没进Spring MVC在响应头添加CORS信息让浏览器放行JwtInterceptor请求进入Spring MVC但在Controller之前校验Token无效则直接返回401 JSONResultController返回数据时将所有返回统一包装成{code,msg,data}格式一条请求的生命周期浏览器发请求 → CorsFilter 检查跨域放行。请求到DispatcherServlet → JwtInterceptor 检查Token登录接口除外。Token有效进入Controller业务处理返回Result.success(data)。Spring MVC将Result转成JSON返回前端。