java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > SpringBoot过滤器与跨域配置

SpringBoot过滤器与跨域配置的三种方案

作者:张老师技术栈

本文详解SpringBoot三种跨域解决方案:注解、全局配置和Filter手动处理,并对比Filter与Interceptor的区别和使用场景,助你彻底解决跨域难题,提升开发效率,需要的朋友可以参考下

前后端分离项目中,跨域问题是最常见的拦路虎。这篇讲 SpringBoot 中三种解决跨域的方式,以及 Filter 和 Interceptor 的区别与使用场景。

一、什么是跨域

1. 跨域的产生

前端地址:http://localhost:8080
后端地址:http://localhost:9090
              ↑ 端口不同,产生了跨域

浏览器的同源策略:
  协议相同、域名相同、端口相同 → 同源
  任何一个不同 → 跨域

2. 跨域的表现

浏览器控制台报错:
Access to XMLHttpRequest at 'http://localhost:9090/api/users'
from origin 'http://localhost:8080' has been blocked by CORS policy

二、三种跨域解决方案

方案一:@CrossOrigin 注解

最简单的方式,在 Controller 或方法上加注解:

@RestController
@RequestMapping("/api/users")
@CrossOrigin(origins = "http://localhost:8080")
public class UserController {
    // 整个类都允许跨域
}

// 或者只允许某个方法跨域
@GetMapping("/{id}")
@CrossOrigin(origins = "*")  // 允许所有来源
public ResultVO<User> get(@PathVariable Long id) {
    return ResultVO.success(userService.getById(id));
}

缺点: 每个 Controller 都要加,维护麻烦。

方案二:全局配置(推荐)

@Configuration
public class CorsConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/api/**")              // 允许跨域的路径
                .allowedOriginPatterns("*")          // 允许的域名
                .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
                .allowedHeaders("*")
                .allowCredentials(true)              // 允许携带 Cookie
                .maxAge(3600);                       // 预检请求缓存时间
    }
}

注意: allowCredentials(true)allowedOriginPatterns("*") 必须同时使用,不能单独用 allowedOrigins("*"),否则会报错。

方案三:Filter 手动处理

@Component
@Order(1)
public class CorsFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
                         FilterChain chain) throws IOException, ServletException {
        HttpServletResponse resp = (HttpServletResponse) response;
        resp.setHeader("Access-Control-Allow-Origin", "*");
        resp.setHeader("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE,OPTIONS");
        resp.setHeader("Access-Control-Allow-Headers", "*");
        resp.setHeader("Access-Control-Max-Age", "3600");

        // 预检请求直接返回
        if ("OPTIONS".equalsIgnoreCase(((HttpServletRequest) request).getMethod())) {
            resp.setStatus(HttpServletResponse.SC_OK);
            return;
        }

        chain.doFilter(request, response);
    }
}

三、Filter vs Interceptor

对比Filter(过滤器)Interceptor(拦截器)
层级Servlet 层面Spring 层面
范围所有请求只有进入 Controller 的请求
操作HttpServletRequest/Response方法调用前后处理
使用场景CORS、编码、鉴权 Token 校验登录校验、日志记录、权限验证

Filter 典型场景:请求日志

@Component
@Order(2)
public class RequestLogFilter implements Filter {

    private static final Logger log = LoggerFactory.getLogger(RequestLogFilter.class);

    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
                         FilterChain chain) throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) request;
        long start = System.currentTimeMillis();

        chain.doFilter(request, response);

        long duration = System.currentTimeMillis() - start;
        log.info("{} {} {} - {}ms",
            req.getMethod(),
            req.getRequestURI(),
            response.getContentType(),
            duration
        );
    }
}

Interceptor 典型场景:登录校验

@Component
public class LoginInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request,
                             HttpServletResponse response,
                             Object handler) throws Exception {
        // 从请求头获取 Token
        String token = request.getHeader("Authorization");
        if (token == null || token.isEmpty()) {
            response.setStatus(401);
            response.setContentType("application/json");
            response.getWriter().write("{\"code\":401,\"message\":\"未登录\"}");
            return false;
        }

        // 校验 Token(省略具体逻辑)
        // if (!TokenUtil.validate(token)) { return false; }

        return true;
    }
}
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {

    @Autowired
    private LoginInterceptor loginInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginInterceptor)
                .addPathPatterns("/api/**")
                .excludePathPatterns("/api/login", "/api/register", "/doc.html");
    }
}

四、XSS 过滤器

@Component
@Order(3)
public class XssFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
                         FilterChain chain) throws IOException, ServletException {
        chain.doFilter(new XssHttpServletRequestWrapper(
            (HttpServletRequest) request), response);
    }

    /**
     * 包装请求,过滤 XSS 脚本
     */
    static class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {

        public XssHttpServletRequestWrapper(HttpServletRequest request) {
            super(request);
        }

        @Override
        public String getParameter(String name) {
            String value = super.getParameter(name);
            return cleanXss(value);
        }

        @Override
        public String[] getParameterValues(String name) {
            String[] values = super.getParameterValues(name);
            if (values == null) return null;
            String[] cleaned = new String[values.length];
            for (int i = 0; i < values.length; i++) {
                cleaned[i] = cleanXss(values[i]);
            }
            return cleaned;
        }

        private String cleanXss(String value) {
            if (value == null) return null;
            // 过滤常见 XSS 关键字
            value = value.replaceAll("<script>", "")
                         .replaceAll("</script>", "")
                         .replaceAll("onerror", "")
                         .replaceAll("onclick", "");
            return value;
        }
    }
}

五、Filter 的执行顺序

/**
 * 多个 Filter 的执行顺序:
 *
 * CorsFilter(@Order(1))→ RequestLogFilter(@Order(2))→ XssFilter(@Order(3))
 *    ↓
 * DispatcherServlet → Interceptor(preHandle)
 *    ↓
 * Controller
 *    ↓
 * Interceptor(postHandle)
 *    ↓
 * Interceptor(afterCompletion)
 *    ↓
 * Filter(反向执行)
 */

执行顺序:

请求进来
  → CorsFilter.doFilter()
  → RequestLogFilter.doFilter()  
  → XssFilter.doFilter()
  → LoginInterceptor.preHandle()
  → Controller 方法
  → LoginInterceptor.afterCompletion()
  → XssFilter.doFilter() 结束
  → RequestLogFilter.doFilter() 结束
  → CorsFilter.doFilter() 结束
  → 响应返回

六、常见问题

1. OPTIONS 预检请求

浏览器在发送真正的跨域请求之前,会先发一个 OPTIONS 请求
检查服务器是否允许跨域

如果 OPTIONS 请求没有正常返回,真正的请求也不会发出

2. 携带 Cookie 的跨域

// 前端需要设置
axios.defaults.withCredentials = true;

// 后端不能使用 allowedOrigins("*")
// 必须指定具体的域名
allowedOriginPatterns("http://localhost:8080")
allowCredentials(true)

3. Nginx 解决跨域

server {
    listen 80;
    location /api/ {
        proxy_pass http://localhost:9090/;
        # 跨域配置
        add_header 'Access-Control-Allow-Origin' '*';
        add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
        if ($request_method = 'OPTIONS') {
            return 204;
        }
    }
}

七、秒杀系统的跨域配置

@Configuration
public class SeckillWebConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/api/**")
                .allowedOriginPatterns("*")
                .allowedMethods("GET", "POST", "PUT", "DELETE")
                .allowCredentials(true)
                .maxAge(3600);
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new SeckillInterceptor())
                .addPathPatterns("/api/seckill/**")
                .excludePathPatterns("/api/seckill/init/**");
    }
}

总结

简单跨域 → @CrossOrigin(临时调试用)
生产环境 → 全局 CORS 配置(WebMvcConfigurer)
特殊需求 → Filter 手动处理
权限校验 → Interceptor(Spring 层面更灵活)

到此这篇关于SpringBoot过滤器与跨域配置的三种方案的文章就介绍到这了,更多相关SpringBoot过滤器与跨域配置内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文