news 2026/5/13 0:51:29

【SpringBoot 从入门到架构师】第7章:拦截器、过滤器、跨域处理

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【SpringBoot 从入门到架构师】第7章:拦截器、过滤器、跨域处理

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"); } }
方法二:使用 FilterRegistrationBean
@Configuration public class FilterConfig { @Bean public FilterRegistrationBean<FirstFilter> firstFilter() { FilterRegistrationBean<FirstFilter> registration = new FilterRegistrationBean<>(); registration.setFilter(new FirstFilter()); registration.addUrlPatterns("/*"); registration.setOrder(1); // 设置顺序 registration.setName("firstFilter"); return registration; } @Bean public FilterRegistrationBean<SecondFilter> secondFilter() { FilterRegistrationBean<SecondFilter> 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 ThreadLocal<Long> 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 RedisTemplate<String, 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;charset=UTF-8"); Map<String, 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 ThreadLocal<UserDTO> 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. 获取用户权限列表(可以从数据库或缓存获取) Set<String> userPermissions = getUserPermissions(currentUser.getId()); // 5. 校验权限 if (!checkPermission(userPermissions, permission)) { returnForbidden(response, "权限不足"); return false; } return true; } private boolean checkPermission(Set<String> 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 Set<String> 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;charset=UTF-8"); Map<String, 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 ThreadLocal<Long> startTime = new ThreadLocal<>(); private ThreadLocal<Map<String, Object>> logInfo = new ThreadLocal<>(); @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 记录开始时间 startTime.set(System.currentTimeMillis()); // 收集日志信息 Map<String, 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 { Map<String, 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 Map<String, String[]> getRequestParameters(HttpServletRequest request) { Map<String, 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; } }

五、最佳实践建议

  1. 拦截器执行顺序 :通过order()方法控制执行顺序,数值越小优先级越高
  2. 性能考虑 :
  1. 避免在拦截器中执行耗时操作
  2. 使用缓存存储频繁访问的数据
  3. 合理设置排除路径
  1. 线程安全 :
  1. 使用ThreadLocal存储线程相关数据
  2. 确保在afterCompletion中清理ThreadLocal
  1. 日志记录优化 :
  1. 使用异步方式记录日志
  2. 敏感信息脱敏处理
  3. 控制日志输出级别
  1. 扩展性 :
  1. 使用注解方式配置权限
  2. 支持多种认证方式(JWT、Session等)
  3. 可配置的权限验证逻辑

这样配置的拦截器系统可以很好地处理登录验证、权限控制和日志记录,同时保持了代码的清晰和可维护性。

3. 过滤器与拦截器区别、执行流程详解

一、核心区别对比

特性

过滤器 (FILTER)

拦截器 (INTERCEPTOR)

规范

Servlet 规范 (J2EE 标准)

Spring MVC 框架特有

依赖

依赖 Servlet 容器

依赖 Spring 容器

作用范围

所有请求 (包括静态资源)

只针对 Spring MVC 请求

获取对象

ServletRequest/ServletResponse

HttpServletRequest/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 FilterRegistrationBean<Filter> authFilter() { FilterRegistrationBean<Filter> 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); } }

四、使用场景建议

适合使用过滤器的场景:
  1. 全局跨域处理 - 所有请求都需要
  2. 字符编码设置 - 统一请求/响应编码
  3. XSS防护 - 过滤危险字符
  4. 请求/响应日志记录 - 记录所有请求
  5. 性能监控 - 统计请求耗时
  6. GZIP压缩 - 响应内容压缩
适合使用拦截器的场景:
  1. 权限验证 - 需要访问Spring Bean
  2. 登录检查 - 基于Session或Token
  3. 参数预处理 - 统一参数处理
  4. 接口限流 - 需要计数器等业务逻辑
  5. 审计日志 - 记录业务操作
  6. 国际化处理 - 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-methods=GET,POST,PUT,DELETE,OPTIONS spring.mvc.cors.allowed-headers=* spring.mvc.cors.allow-credentials=true spring.mvc.cors.max-age=3600

生产环境建议配置

@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("*")​​ 冲突

// 错误:当 allowCredentials=true 时,不能使用 allowedOrigins("*") // 正确:使用 allowedOriginPatterns("*") 或指定具体域名 .allowedOriginPatterns("http://localhost:3000", "https://example.com") .allowCredentials(true)

问题2:OPTIONS 预检请求处理

确保服务器正确处理 OPTIONS 请求,Spring Boot 默认会处理。

问题3:响应头被浏览器拦截

使用 ​​exposedHeaders​​ 暴露需要前端访问的响应头:

.exposedHeaders("Authorization", "X-Total-Count")

建议

  1. 开发环境 :可以使用通配符*简化配置
  2. 生产环境 :建议指定具体的域名,增强安全性
  3. 微服务环境 :可以在网关层统一处理 CORS
  4. 前后端分离项目 :推荐使用WebMvcConfigurer方式

选择哪种方式取决于具体需求,对于大多数 Spring Boot 项目,方式2(实现 ​​WebMvcConfigurer​​)是最常用且推荐的方式 。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/13 0:47:50

重庆大学LaTeX毕业论文模板:3步完成专业论文排版的完整指南

重庆大学LaTeX毕业论文模板&#xff1a;3步完成专业论文排版的完整指南 【免费下载链接】CQUThesis :pencil: 重庆大学毕业论文LaTeX模板---LaTeX Thesis Template for Chongqing University 项目地址: https://gitcode.com/gh_mirrors/cq/CQUThesis 还在为毕业论文格式…

作者头像 李华
网站建设 2026/5/13 0:47:48

利用Taotoken模型广场为智能客服场景选择合适的对话模型

&#x1f680; 告别海外账号与网络限制&#xff01;稳定直连全球优质大模型&#xff0c;限时半价接入中。 &#x1f449; 点击领取海量免费额度 利用Taotoken模型广场为智能客服场景选择合适的对话模型 为智能客服系统选择对话模型&#xff0c;是一个需要平衡响应速度、成本与…

作者头像 李华
网站建设 2026/5/13 0:46:38

5步解决网易云音乐NCM文件难题:ncmdumpGUI实战指南

5步解决网易云音乐NCM文件难题&#xff1a;ncmdumpGUI实战指南 【免费下载链接】ncmdumpGUI C#版本网易云音乐ncm文件格式转换&#xff0c;Windows图形界面版本 项目地址: https://gitcode.com/gh_mirrors/nc/ncmdumpGUI 你是否曾经遇到过这样的情况&#xff1a;在网易…

作者头像 李华
网站建设 2026/5/13 0:45:34

STM32G474定时器抖动模式:解锁PWM超分辨率输出的实战指南

1. STM32G474定时器抖动模式初探 第一次在STM32G474参考手册里看到"抖动模式"这个词时&#xff0c;我盯着屏幕愣了半天。当时已经是凌晨两点&#xff0c;我正为一个LED调光项目发愁——客户要求0.1%的亮度调节精度&#xff0c;但标准PWM分辨率根本达不到。就在准备放…

作者头像 李华
网站建设 2026/5/13 0:42:23

在Node.js后端服务中集成Taotoken调用大模型指南

&#x1f680; 告别海外账号与网络限制&#xff01;稳定直连全球优质大模型&#xff0c;限时半价接入中。 &#x1f449; 点击领取海量免费额度 在Node.js后端服务中集成Taotoken调用大模型指南 将大模型能力集成到后端服务是现代应用开发的常见需求。Taotoken平台提供了OpenA…

作者头像 李华