Java案例如何实现跨域处理?

wen java案例 28

Java案例如何实现跨域处理?一文详解跨域解决方案与实战代码

目录导读

  • 什么是跨域?为什么会出现跨域问题?
  • 跨域问题的本质与常见场景分析
  • Java后端解决跨域的四大主流方案
  • 使用CORS过滤器(最常用)
  • Spring框架的@CrossOrigin注解
  • JSONP方案(仅限GET请求)
  • Nginx反向代理跨域
  • 常见问题与问答汇总
  • 跨域处理的最佳实践建议

什么是跨域?为什么会出现跨域问题?

跨域(Cross-Origin) 指的是浏览器在请求不同源(协议、域名、端口任一不同)的资源时,出于安全考虑,会限制这种请求,这是浏览器的同源策略(Same-Origin Policy) 所规定的安全机制。

Java案例如何实现跨域处理?

前端部署在 http://localhost:8080,后端接口部署在 http://localhost:8081,这两个地址端口不同,因此属于跨域请求,浏览器会直接拦截响应。

常见跨域场景

前端地址 后端地址 是否跨域
http://a.com http://a.com/api
http://a.com http://b.com/api 是(域名不同)
http://a.com:80 http://a.com:8080 是(端口不同)
http://a.com https://a.com 是(协议不同)

注意:跨域是浏览器行为,服务器之间直接通信不存在跨域问题。


跨域问题的本质与常见场景分析

跨域问题的本质是浏览器阻止了前端JavaScript读取跨域请求的响应内容,实际场景中,跨域通常出现在以下情况:

  1. 前后端分离开发:前端Vue/React项目运行在localhost:3000,后端Spring Boot运行在localhost:8081
  2. 微服务架构:多个服务部署在不同域名或端口下
  3. 第三方API调用:调用外部API时(如百度地图、微信接口)

Java后端解决跨域的四大主流方案

在Java生态中,最常见的四种跨域解决方案:CORS过滤器、@CrossOrigin注解、JSONP、Nginx反向代理,其中CORS过滤器是通用性最强、最推荐的方式


使用CORS过滤器(最常用)

这是企业级项目中最推荐的跨域处理方式,通过自定义一个Filter,添加响应头来允许跨域访问。

核心原理

CORS(Cross-Origin Resource Sharing)通过在HTTP响应头中添加以下字段实现跨域:

Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization

完整Java代码实现

import javax.servlet.*;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
public class CorsFilter implements Filter {
    @Override
    public void doFilter(ServletRequest servletRequest, 
                         ServletResponse servletResponse, 
                         FilterChain filterChain) throws IOException, ServletException {
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        // 允许所有域名访问(建议生产环境指定具体域名)
        response.setHeader("Access-Control-Allow-Origin", "*");
        // 允许的HTTP方法
        response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
        // 允许的自定义请求头
        response.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Requested-With");
        // 允许携带Cookie
        response.setHeader("Access-Control-Allow-Credentials", "true");
        // 预检请求缓存时间(秒)
        response.setHeader("Access-Control-Max-Age", "3600");
        // 对于OPTIONS预检请求,直接返回200
        if ("OPTIONS".equalsIgnoreCase(((HttpServletRequest) servletRequest).getMethod())) {
            response.setStatus(HttpServletResponse.SC_OK);
            return;
        }
        filterChain.doFilter(servletRequest, servletResponse);
    }
}

配置Filter注册(Spring Boot)

@Configuration
public class WebConfig {
    @Bean
    public FilterRegistrationBean<CorsFilter> corsFilterRegistration() {
        FilterRegistrationBean<CorsFilter> registration = new FilterRegistrationBean<>();
        registration.setFilter(new CorsFilter());
        registration.addUrlPatterns("/*");  // 拦截所有请求
        registration.setName("corsFilter");
        registration.setOrder(1);
        return registration;
    }
}

优点:全局生效,一次配置,所有接口自动支持跨域;支持所有HTTP方法。

缺点:如果前后端都需要指定具体域名,需要额外配置。


Spring框架的@CrossOrigin注解

如果你的项目使用Spring Boot,可以不必写完整的Filter,直接使用@CrossOrigin注解。

在Controller类上加注解(全局)

@RestController
@CrossOrigin(origins = "http://localhost:3000")  // 允许前端地址
@RequestMapping("/api")
public class UserController {
    @GetMapping("/users")
    public List<User> getUsers() {
        // 业务逻辑
    }
}

在具体方法上加注解(细粒度)

@RestController
@RequestMapping("/api")
public class UserController {
    @CrossOrigin(origins = "http://localhost:3000")
    @GetMapping("/users")
    public List<User> getUsers() {
        // 业务逻辑
    }
    // 其他接口不跨域
    @GetMapping("/internal")
    public String internal() {
        return "内部接口";
    }
}

全局配置(WebMvcConfigurer)

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOrigins("http://localhost:3000")  // 指定来源
                .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
                .allowedHeaders("*")
                .allowCredentials(true);
    }
}

优点:使用简单,Spring官方支持。

缺点:必须在Spring环境中使用;如果在Filter层面还有权限校验,需要确保顺序。


JSONP方案(仅限GET请求)

JSONP是一种古老的跨域解决方案,利用<script>标签不受同源策略限制的特性实现。

JSONP原理

  1. 前端动态创建一个<script>标签,src指向后端接口,并带上回调函数名
  2. 后端返回callback(JSON数据)的形式
  3. 浏览器接收到响应后,自动执行回调函数

Java后端实现JSONP

@RestController
@RequestMapping("/api")
public class JsonpController {
    @GetMapping("/users")
    public String getUsersJsonp(@RequestParam("callback") String callback) {
        List<User> users = userService.findAll();
        String json = new Gson().toJson(users);
        // 返回 callback(json) 格式
        return callback + "(" + json + ")";
    }
}

前端调用示例(原生):

function jsonpRequest() {
    let script = document.createElement('script');
    script.src = 'http://localhost:8081/api/users?callback=handleResponse';
    document.body.appendChild(script);
}
function handleResponse(data) {
    console.log('跨域获取的数据:', data);
}

缺点

  • 只支持GET请求
  • 安全性较低(可能被XSS攻击)
  • 难以处理错误(404/500等)

建议:除非维护老旧系统,否则不推荐使用JSONP。


Nginx反向代理跨域

如果不想修改后端代码,可以通过Nginx服务器进行代理,同源的请求由Nginx转发到不同后端服务。

Nginx配置示例

server {
    listen 80;
    server_name api.example.com;
    location /api/ {
        proxy_pass http://localhost:8081/;  # 转发到Java后端
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        # 添加CORS头
        add_header Access-Control-Allow-Origin *;
        add_header Access-Control-Allow-Methods "GET, POST, OPTIONS";
        add_header Access-Control-Allow-Headers "Content-Type, Authorization";
        # 处理OPTIONS预检请求
        if ($request_method = 'OPTIONS') {
            return 204;
        }
    }
    location /static/ {
        alias /var/www/html/;  # 前端静态资源目录
    }
}

前端访问:直接请求http://api.example.com/api/users即可,不会出现跨域问题。

优点:无需修改后端代码;集中管理跨域策略。

缺点:需要额外部署Nginx;请求路径多一层代理,略微增加延迟。


常见问题与问答汇总

问:为什么有时候配置了CORS还是报跨域错误?

:常见原因有三个:

  1. 浏览器同源策略无法禁用:尝试用Postman测试,如果正常则说明CORS配置没问题
  2. 缺少OPTIONS预检请求处理:浏览器发送跨域请求前会先发OPTIONS请求,后端必须返回204或200
  3. Allow-Origin与Allow-Credentials冲突:当allowCredentials(true)时,allowedOrigins不能是,必须指定具体域名

问:前端使用axios时,跨域需要额外配置吗?

:不需要额外配置axios,但需要注意:

  • 如果后端设置了withCredentials: true,前端axios也需要配置axios.defaults.withCredentials = true
  • 如果后端allowedOrigins指定了具体域名,前端请求的URL必须与之一致(区分http和https)

问:跨域和CSRF(跨站请求伪造)有关系吗?

:有关系,跨域请求本身是浏览器行为,而CSRF攻击利用了用户的登录凭证,CORS配置中如果开启了allowCredentials(true),需要配合CSRF防护机制(如添加CSRF Token、SameSite属性等),防止恶意网站利用Cookie发起跨域请求。

问:生产环境下,allowedOrigins应该配置还是具体域名?

强烈建议配置具体域名,允许所有来源访问,包括恶意网站,如果API涉及用户认证(Cookie/Token),绝不能使用,否则会造成严重安全漏洞,正确做法:线上环境将前端部署域名写入白名单。


跨域处理的最佳实践建议

  1. 优先使用CORS过滤器方案:通用性强,不依赖框架,适合所有Java Web项目
  2. 区分开发环境和生产环境
    • 开发环境:allowedOrigins可配置或前端开发地址(如http://localhost:3000
    • 生产环境:务必配置具体域名,且保持与前端部署域名一致
  3. 不要忘记处理OPTIONS请求:预检请求是CORS实现的关键环节
  4. 结合Nginx反向代理:如果公司已有网关服务(Nginx/Kong/Spring Cloud Gateway),将跨域处理放在网关层更合理,后端的Java服务只需关注业务逻辑
  5. 安全优先:开启凭证时(allowCredentials=true),务必指定具体来源,并配合CSRF防护

Java后端跨域处理并不复杂,核心在于理解CORS协议,建议刚入门的开发者直接使用方案一(CORS Filter)方案二的全局配置作为企业级开发标配,同时保留Nginx反向代理作为运维备选方案,在实际项目中,根据团队技术栈和安全要求,选择合适的方案即可。

抱歉,评论功能暂时关闭!