1. 过滤器Filter自定义过滤器、执行顺序、应用场景一、自定义过滤器基础实现方式方式一实现 Filter 接口Component public class CustomFilter implements Filter { Override public void init(FilterConfig filterConfig) { // 初始化逻辑 } Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { // 请求处理前逻辑 System.out.println(Before filter processing); HttpServletRequest httpRequest (HttpServletRequest) request; System.out.println(Request URI: httpRequest.getRequestURI()); // 执行下一个过滤器或目标资源 chain.doFilter(request, response); // 响应处理后逻辑 System.out.println(After filter processing); } Override public void destroy() { // 销毁逻辑 } }方式二使用 WebFilter 注解WebFilter(urlPatterns /*) public class AnnotationFilter implements Filter { Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { long startTime System.currentTimeMillis(); chain.doFilter(request, response); long endTime System.currentTimeMillis(); System.out.println(Request took: (endTime - startTime) ms); } }需要在启动类添加 ​​ServletComponentScan​​SpringBootApplication ServletComponentScan public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }二、过滤器执行顺序控制执行顺序的方法方法一使用 Order 注解Component Order(1) // 数字越小优先级越高 public class FirstFilter implements Filter { Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { System.out.println(First Filter - Before); chain.doFilter(request, response); System.out.println(First Filter - After); } } Component Order(2) public class SecondFilter implements Filter { Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { System.out.println(Second Filter - Before); chain.doFilter(request, response); System.out.println(Second Filter - After); } }方法二使用 FilterRegistrationBeanConfiguration public class FilterConfig { Bean public FilterRegistrationBeanFirstFilter firstFilter() { FilterRegistrationBeanFirstFilter registration new FilterRegistrationBean(); registration.setFilter(new FirstFilter()); registration.addUrlPatterns(/*); registration.setOrder(1); // 设置顺序 registration.setName(firstFilter); return registration; } Bean public FilterRegistrationBeanSecondFilter secondFilter() { FilterRegistrationBeanSecondFilter registration new FilterRegistrationBean(); registration.setFilter(new SecondFilter()); registration.addUrlPatterns(/*); registration.setOrder(2); registration.setName(secondFilter); return registration; } }执行顺序规则过滤器按照Order值从小到大执行​​FilterRegistrationBean​​ 优先级高于Order注解相同顺序时按 Bean 名称字母顺序执行三、应用场景示例认证和授权过滤器Component Order(1) public class AuthFilter implements Filter { Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpRequest (HttpServletRequest) request; HttpServletResponse httpResponse (HttpServletResponse) response; String token httpRequest.getHeader(Authorization); if (!isValidToken(token)) { httpResponse.setStatus(HttpStatus.UNAUTHORIZED.value()); httpResponse.getWriter().write(Unauthorized); return; } // 将用户信息放入请求属性 UserInfo userInfo parseToken(token); httpRequest.setAttribute(userInfo, userInfo); chain.doFilter(request, response); } private boolean isValidToken(String token) { // 验证 token 逻辑 return token ! null token.startsWith(Bearer ); } private UserInfo parseToken(String token) { // 解析 token 逻辑 return new UserInfo(); } }日志记录过滤器Component public class LoggingFilter implements Filter { private static final Logger logger LoggerFactory.getLogger(LoggingFilter.class); Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpRequest (HttpServletRequest) request; HttpServletResponse httpResponse (HttpServletResponse) response; // 记录请求信息 String requestURI httpRequest.getRequestURI(); String method httpRequest.getMethod(); String clientIP httpRequest.getRemoteAddr(); logger.info(Request started: {} {} from {}, method, requestURI, clientIP); long startTime System.currentTimeMillis(); try { chain.doFilter(request, response); } finally { long duration System.currentTimeMillis() - startTime; int status httpResponse.getStatus(); logger.info(Request completed: {} {} - Status: {} - Duration: {}ms, method, requestURI, status, duration); } } }跨域过滤器Component public class CorsFilter implements Filter { Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletResponse httpResponse (HttpServletResponse) response; HttpServletRequest httpRequest (HttpServletRequest) request; // 设置跨域头 httpResponse.setHeader(Access-Control-Allow-Origin, *); httpResponse.setHeader(Access-Control-Allow-Methods, GET, POST, PUT, DELETE, OPTIONS); httpResponse.setHeader(Access-Control-Max-Age, 3600); httpResponse.setHeader(Access-Control-Allow-Headers, authorization, content-type, xsrf-token); httpResponse.setHeader(Access-Control-Expose-Headers, xsrf-token); // 处理预检请求 if (OPTIONS.equalsIgnoreCase(httpRequest.getMethod())) { httpResponse.setStatus(HttpServletResponse.SC_OK); } else { chain.doFilter(request, response); } } }请求参数处理过滤器Component public class RequestWrapperFilter implements Filter { Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { // 包装请求实现请求体重复读取 if (request instanceof HttpServletRequest) { HttpServletRequest httpRequest (HttpServletRequest) request; // 对特定请求进行处理 if (httpRequest.getContentType() ! null httpRequest.getContentType().contains(application/json)) { CachedBodyHttpServletRequest cachedRequest new CachedBodyHttpServletRequest(httpRequest); chain.doFilter(cachedRequest, response); return; } } chain.doFilter(request, response); } } // 自定义可重复读取的 RequestWrapper class CachedBodyHttpServletRequest extends HttpServletRequestWrapper { private byte[] cachedBody; public CachedBodyHttpServletRequest(HttpServletRequest request) throws IOException { super(request); InputStream requestInputStream request.getInputStream(); this.cachedBody StreamUtils.copyToByteArray(requestInputStream); } Override public ServletInputStream getInputStream() { return new CachedBodyServletInputStream(this.cachedBody); } Override public BufferedReader getReader() { ByteArrayInputStream byteArrayInputStream new ByteArrayInputStream(this.cachedBody); return new BufferedReader(new InputStreamReader(byteArrayInputStream)); } }性能监控过滤器Component Order(Ordered.HIGHEST_PRECEDENCE) public class PerformanceFilter implements Filter { private static final ThreadLocalLong startTimeHolder new ThreadLocal(); Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { startTimeHolder.set(System.currentTimeMillis()); try { chain.doFilter(request, response); } finally { Long startTime startTimeHolder.get(); if (startTime ! null) { long duration System.currentTimeMillis() - startTime; // 记录慢请求 if (duration 1000) { // 超过1秒 HttpServletRequest httpRequest (HttpServletRequest) request; System.err.println(String.format( Slow request: %s %s took %dms, httpRequest.getMethod(), httpRequest.getRequestURI(), duration )); } startTimeHolder.remove(); } } } }总结Spring Boot 过滤器是处理 HTTP 请求的强大工具适用于全局性处理 认证、日志、跨域等性能监控 请求耗时统计数据预处理 请求/响应数据包装安全控制 XSS 防护、SQL 注入防护合理使用过滤器可以提升代码的复用性和可维护性但要注意避免在过滤器中实现复杂的业务逻辑保持职责单一。2. 拦截器Interceptor登录拦截、权限校验、日志记录一、拦截器基础创建拦截器类Component public class AuthInterceptor implements HandlerInterceptor { // 在Controller方法执行之前调用 Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 返回true继续执行false中断请求 return true; } // 在Controller方法执行之后视图渲染之前调用 Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { } // 在整个请求结束之后调用 Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { } }配置拦截器Configuration public class WebConfig implements WebMvcConfigurer { Autowired private AuthInterceptor authInterceptor; Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(authInterceptor) .addPathPatterns(/**) // 拦截所有路径 .excludePathPatterns( // 排除路径 /api/login, /api/register, /swagger-ui/**, /v3/api-docs/** ); } }二、登录拦截实现登录拦截器Component public class LoginInterceptor implements HandlerInterceptor { Autowired private RedisTemplateString, Object redisTemplate; Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 1. 检查是否是预检请求OPTIONS if (OPTIONS.equalsIgnoreCase(request.getMethod())) { return true; } // 2. 获取token String token request.getHeader(Authorization); if (StringUtils.isEmpty(token)) { token request.getParameter(token); } // 3. 验证token if (StringUtils.isEmpty(token)) { returnUnauthorized(response, 未提供认证令牌); return false; } // 4. 验证token有效性这里以Redis存储为例 String userInfo (String) redisTemplate.opsForValue().get(token: token); if (StringUtils.isEmpty(userInfo)) { returnUnauthorized(response, 令牌已过期或无效); return false; } // 5. 解析用户信息并存入请求上下文 UserDTO userDTO JSON.parseObject(userInfo, UserDTO.class); UserContext.setCurrentUser(userDTO); // 6. 刷新token有效期 redisTemplate.expire(token: token, 30, TimeUnit.MINUTES); return true; } Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { // 清理线程上下文防止内存泄漏 UserContext.clear(); } private void returnUnauthorized(HttpServletResponse response, String message) throws IOException { response.setStatus(HttpStatus.UNAUTHORIZED.value()); response.setContentType(application/json;charsetUTF-8); MapString, Object result new HashMap(); result.put(code, 401); result.put(message, message); result.put(timestamp, System.currentTimeMillis()); response.getWriter().write(JSON.toJSONString(result)); } }用户上下文工具类public class UserContext { private static final ThreadLocalUserDTO userHolder new ThreadLocal(); public static void setCurrentUser(UserDTO user) { userHolder.set(user); } public static UserDTO getCurrentUser() { return userHolder.get(); } public static Long getCurrentUserId() { UserDTO user userHolder.get(); return user ! null ? user.getId() : null; } public static void clear() { userHolder.remove(); } }三、权限校验实现权限注解Target(ElementType.METHOD) Retention(RetentionPolicy.RUNTIME) public interface Permission { String[] value() default {}; Logical logical() default Logical.AND; } public enum Logical { AND, OR }权限拦截器Component public class PermissionInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 1. 判断是否是HandlerMethod if (!(handler instanceof HandlerMethod)) { return true; } HandlerMethod handlerMethod (HandlerMethod) handler; // 2. 获取方法上的权限注解 Permission permission handlerMethod.getMethodAnnotation(Permission.class); if (permission null) { return true; // 没有权限注解直接放行 } // 3. 获取当前用户 UserDTO currentUser UserContext.getCurrentUser(); if (currentUser null) { returnForbidden(response, 用户未登录); return false; } // 4. 获取用户权限列表可以从数据库或缓存获取 SetString userPermissions getUserPermissions(currentUser.getId()); // 5. 校验权限 if (!checkPermission(userPermissions, permission)) { returnForbidden(response, 权限不足); return false; } return true; } private boolean checkPermission(SetString userPermissions, Permission permission) { String[] requiredPermissions permission.value(); if (requiredPermissions.length 0) { return true; } if (permission.logical() Logical.AND) { // 需要同时拥有所有权限 for (String required : requiredPermissions) { if (!userPermissions.contains(required)) { return false; } } return true; } else { // 只需要拥有任意一个权限 for (String required : requiredPermissions) { if (userPermissions.contains(required)) { return true; } } return false; } } private SetString getUserPermissions(Long userId) { // 这里可以从数据库或缓存获取用户权限 // 示例返回模拟数据 return new HashSet(Arrays.asList(user:view, user:edit)); } private void returnForbidden(HttpServletResponse response, String message) throws IOException { response.setStatus(HttpStatus.FORBIDDEN.value()); response.setContentType(application/json;charsetUTF-8); MapString, Object result new HashMap(); result.put(code, 403); result.put(message, message); result.put(timestamp, System.currentTimeMillis()); response.getWriter().write(JSON.toJSONString(result)); } }使用示例RestController RequestMapping(/api/user) public class UserController { GetMapping(/list) Permission(user:view) // 需要user:view权限 public Result listUsers() { return Result.success(userService.list()); } PostMapping(/update) Permission({user:view, user:edit}) // 需要同时拥有这两个权限 public Result updateUser(RequestBody User user) { return Result.success(userService.update(user)); } DeleteMapping(/delete) Permission(value {user:delete, admin}, logical Logical.OR) // 拥有任意一个权限即可 public Result deleteUser(RequestParam Long id) { return Result.success(userService.delete(id)); } }四、日志记录实现日志拦截器Component public class LogInterceptor implements HandlerInterceptor { private static final Logger logger LoggerFactory.getLogger(LogInterceptor.class); private ThreadLocalLong startTime new ThreadLocal(); private ThreadLocalMapString, Object logInfo new ThreadLocal(); Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 记录开始时间 startTime.set(System.currentTimeMillis()); // 收集日志信息 MapString, Object info new HashMap(); info.put(requestTime, new Date()); info.put(requestMethod, request.getMethod()); info.put(requestURI, request.getRequestURI()); info.put(clientIP, getClientIP(request)); info.put(userAgent, request.getHeader(User-Agent)); info.put(parameters, getRequestParameters(request)); // 记录用户信息如果已登录 UserDTO currentUser UserContext.getCurrentUser(); if (currentUser ! null) { info.put(userId, currentUser.getId()); info.put(username, currentUser.getUsername()); } logInfo.set(info); // 记录请求日志 logger.info(Request Start: {} {} from {}, request.getMethod(), request.getRequestURI(), getClientIP(request)); return true; } Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { try { MapString, Object info logInfo.get(); if (info ! null) { // 计算执行时间 long executionTime System.currentTimeMillis() - startTime.get(); info.put(executionTime, executionTime); info.put(responseStatus, response.getStatus()); info.put(responseTime, new Date()); // 如果有异常记录异常信息 if (ex ! null) { info.put(exception, ex.getClass().getName()); info.put(exceptionMessage, ex.getMessage()); } // 记录完整的日志 logger.info(Request Completed: {}, JSON.toJSONString(info)); // 慢请求警告 if (executionTime 3000) { // 超过3秒 logger.warn(Slow Request detected: {} {} took {}ms, request.getMethod(), request.getRequestURI(), executionTime); } // 这里可以将日志保存到数据库 // saveLogToDB(info); } } finally { // 清理ThreadLocal startTime.remove(); logInfo.remove(); } } private String getClientIP(HttpServletRequest request) { String ip request.getHeader(X-Forwarded-For); if (ip null || ip.length() 0 || unknown.equalsIgnoreCase(ip)) { ip request.getHeader(Proxy-Client-IP); } if (ip null || ip.length() 0 || unknown.equalsIgnoreCase(ip)) { ip request.getHeader(WL-Proxy-Client-IP); } if (ip null || ip.length() 0 || unknown.equalsIgnoreCase(ip)) { ip request.getRemoteAddr(); } return ip; } private MapString, String[] getRequestParameters(HttpServletRequest request) { MapString, String[] params new HashMap(request.getParameterMap()); // 敏感信息脱敏处理 if (params.containsKey(password)) { params.put(password, new String[]{******}); } if (params.containsKey(token)) { params.put(token, new String[]{******}); } return params; } }五、最佳实践建议拦截器执行顺序 通过order()方法控制执行顺序数值越小优先级越高性能考虑 避免在拦截器中执行耗时操作使用缓存存储频繁访问的数据合理设置排除路径线程安全 使用ThreadLocal存储线程相关数据确保在afterCompletion中清理ThreadLocal日志记录优化 使用异步方式记录日志敏感信息脱敏处理控制日志输出级别扩展性 使用注解方式配置权限支持多种认证方式JWT、Session等可配置的权限验证逻辑这样配置的拦截器系统可以很好地处理登录验证、权限控制和日志记录同时保持了代码的清晰和可维护性。3. 过滤器与拦截器区别、执行流程详解一、核心区别对比特性过滤器 (FILTER)拦截器 (INTERCEPTOR)规范Servlet 规范 (J2EE 标准)Spring MVC 框架特有依赖依赖 Servlet 容器依赖 Spring 容器作用范围所有请求 (包括静态资源)只针对 Spring MVC 请求获取对象ServletRequest/ServletResponseHttpServletRequest/HttpServletResponse获取 Bean无法直接获取 Spring Bean可以直接获取 Spring Bean执行位置Servlet 之前/之后Handler 执行前后异常处理在 doFilter 中处理在 afterCompletion 中处理二、执行流程详解完整请求处理流程客户端请求 ↓ Servlet 容器接收 ↓ FilterChain.doFilter() 开始 ↓ 过滤器1 → 过滤器2 → ... → 过滤器N ↓ DispatcherServlet ↓ HandlerMapping 找到处理器 ↓ 拦截器1.preHandle() ↓ 拦截器2.preHandle() ↓ ... ↓ 拦截器N.preHandle() ↓ Controller 方法执行 ↓ 拦截器N.postHandle() ↓ ... ↓ 拦截器1.postHandle() ↓ 视图渲染 (如果有) ↓ 拦截器1.afterCompletion() ↓ ... ↓ 拦截器N.afterCompletion() ↓ 过滤器N → ... → 过滤器2 → 过滤器1 ↓ 响应返回客户端过滤器执行流程// 过滤器执行顺序 public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) { // 1. 前置处理 (在Servlet之前执行) System.out.println(Filter前置处理); // 2. 传递给下一个过滤器或Servlet chain.doFilter(request, response); // 3. 后置处理 (在Servlet之后执行) System.out.println(Filter后置处理); }拦截器执行流程public class CustomInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { // 1. Controller方法执行前调用 // 返回true继续执行false中断 return true; } Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) { // 2. Controller方法执行后视图渲染前调用 } Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { // 3. 整个请求完成后调用 // 可用于资源清理、异常处理 } }三、代码示例过滤器实现示例// 方式1使用 Component Order Component Order(1) // 定义执行顺序 public class LogFilter implements Filter { Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { long start System.currentTimeMillis(); HttpServletRequest req (HttpServletRequest) request; // 前置处理 System.out.println(请求URI: req.getRequestURI()); try { chain.doFilter(request, response); } finally { // 后置处理 long duration System.currentTimeMillis() - start; System.out.println(请求耗时: duration ms); } } } // 方式2使用 FilterRegistrationBean更灵活 Configuration public class FilterConfig { Bean public FilterRegistrationBeanFilter authFilter() { FilterRegistrationBeanFilter registration new FilterRegistrationBean(); registration.setFilter(new AuthFilter()); registration.addUrlPatterns(/api/*); registration.setOrder(2); registration.setName(authFilter); return registration; } }拦截器实现示例Component public class AuthInterceptor implements HandlerInterceptor { Autowired private UserService userService; // 可以注入Spring Bean Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token request.getHeader(Authorization); // 验证token if (!userService.validateToken(token)) { response.setStatus(HttpStatus.UNAUTHORIZED.value()); response.getWriter().write(Unauthorized); return false; // 中断请求 } // 设置用户信息到请求属性 User user userService.getUserByToken(token); request.setAttribute(currentUser, user); return true; // 继续执行 } Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) { // 可以修改ModelAndView if (modelAndView ! null) { modelAndView.addObject(processed, true); } } Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { // 记录异常日志 if (ex ! null) { log.error(请求处理异常, ex); } } } // 注册拦截器 Configuration public class WebMvcConfig implements WebMvcConfigurer { Autowired private AuthInterceptor authInterceptor; Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(authInterceptor) .addPathPatterns(/api/**) .excludePathPatterns(/api/public/**) .order(1); registry.addInterceptor(new LogInterceptor()) .addPathPatterns(/**) .order(2); } }四、使用场景建议适合使用过滤器的场景全局跨域处理 - 所有请求都需要字符编码设置 - 统一请求/响应编码XSS防护 - 过滤危险字符请求/响应日志记录 - 记录所有请求性能监控 - 统计请求耗时GZIP压缩 - 响应内容压缩适合使用拦截器的场景权限验证 - 需要访问Spring Bean登录检查 - 基于Session或Token参数预处理 - 统一参数处理接口限流 - 需要计数器等业务逻辑审计日志 - 记录业务操作国际化处理 - Locale解析4. 全局跨域配置CORS解决前后端跨域问题使用 ​​CrossOrigin​​ 注解控制器级别RestController RequestMapping(/api) CrossOrigin(origins *) // 允许所有来源 public class MyController { // 或者可以在方法级别使用 GetMapping(/test) CrossOrigin(origins http://localhost:3000) public String test() { return Hello; } }实现 ​​WebMvcConfigurer​​推荐import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) // 所有接口 .allowedOriginPatterns(*) // 支持通配符Spring Boot 2.4 // .allowedOrigins(*) // Spring Boot 2.4 之前 .allowedMethods(GET, POST, PUT, DELETE, OPTIONS) .allowedHeaders(*) .allowCredentials(true) .maxAge(3600); // 预检请求的有效期 } }使用 ​​CorsFilter​​过滤器方式import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import org.springframework.web.filter.CorsFilter; Configuration public class GlobalCorsConfig { Bean public CorsFilter corsFilter() { CorsConfiguration config new CorsConfiguration(); // 允许所有域名进行跨域调用 config.addAllowedOriginPattern(*); // 允许跨越发送cookie config.setAllowCredentials(true); // 放行全部原始头信息 config.addAllowedHeader(*); // 允许所有请求方法跨域调用 config.addAllowedMethod(*); UrlBasedCorsConfigurationSource source new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration(/**, config); return new CorsFilter(source); } }Spring Security 中的 CORS 配置如果使用了 Spring Security需要额外配置import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.web.SecurityFilterChain; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.CorsConfigurationSource; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; Configuration public class SecurityConfig { Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .cors(cors - cors.configurationSource(corsConfigurationSource())) .csrf(csrf - csrf.disable()) // 如果使用JWT等无状态认证可以禁用CSRF .authorizeHttpRequests(auth - auth .requestMatchers(/api/public/**).permitAll() .anyRequest().authenticated() ); return http.build(); } Bean public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration configuration new CorsConfiguration(); configuration.setAllowedOriginPatterns(Arrays.asList(*)); configuration.setAllowedMethods(Arrays.asList(GET, POST, PUT, DELETE, OPTIONS)); configuration.setAllowedHeaders(Arrays.asList(*)); configuration.setAllowCredentials(true); configuration.setMaxAge(3600L); UrlBasedCorsConfigurationSource source new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration(/**, configuration); return source; } }配置文件方式application.yml/properties# application.yml spring: mvc: cors: allowed-origins: * allowed-methods: GET,POST,PUT,DELETE,OPTIONS allowed-headers: * allow-credentials: true max-age: 3600# application.properties spring.mvc.cors.allowed-origins* spring.mvc.cors.allowed-methodsGET,POST,PUT,DELETE,OPTIONS spring.mvc.cors.allowed-headers* spring.mvc.cors.allow-credentialstrue spring.mvc.cors.max-age3600生产环境建议配置Configuration public class CorsConfig implements WebMvcConfigurer { Value(${cors.allowed-origins:*}) private String[] allowedOrigins; Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/api/**) .allowedOriginPatterns(allowedOrigins) .allowedMethods(GET, POST, PUT, DELETE, PATCH, OPTIONS) .allowedHeaders(Authorization, Content-Type, X-Requested-With) .exposedHeaders(Authorization) // 允许前端获取的响应头 .allowCredentials(true) .maxAge(3600); // 可以配置多个路径规则 registry.addMapping(/public/**) .allowedOriginPatterns(*) .allowedMethods(GET, OPTIONS); } }常见问题解决问题1​​allowCredentials​​ 与 ​​allowedOrigins(*)​​ 冲突// 错误当 allowCredentialstrue 时不能使用 allowedOrigins(*) // 正确使用 allowedOriginPatterns(*) 或指定具体域名 .allowedOriginPatterns(http://localhost:3000, https://example.com) .allowCredentials(true)问题2OPTIONS 预检请求处理确保服务器正确处理 OPTIONS 请求Spring Boot 默认会处理。问题3响应头被浏览器拦截使用 ​​exposedHeaders​​ 暴露需要前端访问的响应头.exposedHeaders(Authorization, X-Total-Count)建议开发环境 可以使用通配符*简化配置生产环境 建议指定具体的域名增强安全性微服务环境 可以在网关层统一处理 CORS前后端分离项目 推荐使用WebMvcConfigurer方式选择哪种方式取决于具体需求对于大多数 Spring Boot 项目方式2实现 ​​WebMvcConfigurer​​是最常用且推荐的方式 。