交通银行1面:SpringBoot如何解决跨域问题?

3,577 阅读6分钟

前言

大家好,我是Leo哥😎😎😎,今天我们开启一个新的专栏,聚焦于面试实战,带大家学习真实的公司面试题目,分析背后的技术要点和解决思路。本期我们将深入探讨 Spring Boot如何解决跨域问题 这一经典面试题,希望通过这个专题,帮助大家在面试中脱颖而出,同时夯实实际开发中的技术能力。

无论你是刚入行的后端开发新手,还是有一定经验的开发工程师,这个专题都将为你提供全面的技术解读和实用技巧。接下来,让我们一起来破解这个问题吧!

跨域问题

什么是跨域

跨域是指当一个网页从一个域(比如 example.com)发起请求,试图访问另一个域(比如 api.example.com)上的资源时,因浏览器的同源策略限制而导致的访问问题。同源策略是浏览器的一项安全机制,用于防止恶意网站窃取用户数据。

以下情况下,会被视为 跨域

同源策略

同源策略(Same Origin Policy)规定:

  • 一个页面只能读取来自相同源的资源。
  • 如果协议、域名、端口三者有任何一个不同,就会触发跨域问题。

源策略应用的场景

  • Cookie、LocalStorage 等存储。
  • DOM 和 JavaScript 脚本。
  • AJAX 请求。

CORS

CORS(Cross-Origin Resource Sharing,跨域资源共享)是一种由 W3C 标准定义的跨域请求机制,允许浏览器通过设置 HTTP 请求头和响应头来实现跨域访问。CORS 是浏览器针对跨域请求的安全策略扩展,通过服务端显式声明哪些资源可以被特定来源访问,从而安全地实现跨域资源共享。

简单来说,CORS 是一种由服务端控制跨域请求的安全策略,通过设置特定的 HTTP 响应头,告诉浏览器是否允许该请求跨域访问资源

SpringBoot如何解决跨域?

常见的跨域方式大体来说有几种。

  1. 全局配置CORS
  2. 使用@CrossOrigin注解
  3. 自定义过滤器
  4. Nginx 配置反向代理解决跨域

通过 WebMvcConfigurer 全局配置

如果希望为整个项目启用跨域支持,可以使用 WebMvcConfigurer 来配置。

代码示例

  1. 配置类代码
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 GlobalCorsConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")  // 匹配所有路径
                .allowedOrigins("http://example.com", "http://another-domain.com")  // 允许的跨域域名
                .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")  // 允许的请求方法
                .allowedHeaders("*")  // 允许的请求头
                .allowCredentials(true)  // 是否允许发送 Cookie
                .maxAge(3600);  // 预检请求的缓存时间
    }
}

详细说明

  1. 测试代码
@RestController
public class TestController {

    @GetMapping("/test")
    public String test() {
        return "Global CORS Test Successful!";
    }
}

启动后,访问 example.com 下的前端可以正常调用此接口。

通过 @CrossOrigin注解

Spring 提供了 @CrossOrigin 注解,用于在控制器或方法级别启用跨域支持。

  1. 单个控制器方法启用跨域
@RestController
@RequestMapping("/api")
public class ExampleController {

    @GetMapping("/hello")
    @CrossOrigin(origins = "http://example.com", maxAge = 3600)  // 设置允许跨域的来源和缓存时间
    public String hello() {
        return "Hello, CORS!";
    }
}
  • origins:指定允许跨域的域名(如 example.com)。
  • maxAge:预检请求的缓存时间,单位为秒。
  1. 全控制器启用跨域
@RestController
@RequestMapping("/api")
@CrossOrigin(origins = "http://example.com", allowedHeaders = "*", methods = {RequestMethod.GET, RequestMethod.POST})
public class ExampleController {

    @GetMapping("/test")
    public String getData() {
        return "This is test api";
    }
}

适用场景

  • 某些 API 特定需要跨域支持时使用。
  • 不适用于全局跨域需求。

自定义过滤器

如果需要更高级的跨域控制,比如动态允许某些域名跨域,可以通过自定义 CorsFilter 来实现。

  1. 自定义 CORS 过滤器
import org.springframework.stereotype.Component;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Component
public class CorsFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        // 初始化方法,可选
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        // 强制类型转换为 HttpServletRequest 和 HttpServletResponse
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;

        // 添加 CORS 响应头
        httpResponse.setHeader("Access-Control-Allow-Origin", "*"); // 允许所有来源访问
        httpResponse.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS"); // 允许的 HTTP 方法
        httpResponse.setHeader("Access-Control-Allow-Headers", "Origin, Content-Type, Accept, Authorization"); // 允许的请求头
        httpResponse.setHeader("Access-Control-Allow-Credentials", "true"); // 是否允许携带 Cookie

        // 处理 OPTIONS 请求(CORS 预检请求)
        if ("OPTIONS".equalsIgnoreCase(httpRequest.getMethod())) {
            httpResponse.setStatus(HttpServletResponse.SC_OK);
            return;
        }

        // 继续执行下一个过滤器链
        chain.doFilter(request, response);
    }

    @Override
    public void destroy() {
        // 销毁方法,可选
    }
}
  1. 注册过滤器(可选) 默认情况下,@Component 注解已经将过滤器注册到了容器中。如果需要精确控制过滤器的顺序,可以使用 FilterRegistrationBean。
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class FilterConfig {

    @Bean
    public FilterRegistrationBean<CorsFilter> corsFilterRegistration() {
        FilterRegistrationBean<CorsFilter> registrationBean = new FilterRegistrationBean<>();
        registrationBean.setFilter(new CorsFilter());
        registrationBean.addUrlPatterns("/*"); // 过滤所有 URL
        registrationBean.setOrder(1); // 设置过滤器的优先级,数字越小优先级越高
        return registrationBean;
    }
}

测试与验证

测试方法

  1. 启动 Spring Boot 服务。

  2. 使用带有跨域请求的前端应用(例如,Vue.js 或 React)发起请求。

  3. 打开浏览器的开发者工具,查看请求和响应头,确保响应中包含以下头信息:

    • Access-Control-Allow-Origin

    • Access-Control-Allow-Methods

    • Access-Control-Allow-Headers

    • Access-Control-Allow-Credentials

注意事项

  1. Access-Control-Allow-Origin 设置
    • 如果需要限制特定的来源访问,可以将 "*" 替换为具体的域名,例如 "example.com"。
  2. 安全性
    • 如果允许所有来源访问(*),可能存在安全风险,尤其是在涉及敏感数据的场景下。
  3. OPTIONS 请求
    • 如果未正确处理 OPTIONS 请求,可能导致前端跨域失败。

Nginx配置反向代理解决跨域

有时跨域问题并非一定需要由 Spring Boot 处理,而是可以通过 Nginx 的反向代理解决。

配置示例

  1. Nginx 配置文件
server {
    listen 80;
    server_name xxxxxx.com;

    location /api/ {
        proxy_pass http://backend-service:8080/;  # 后端服务地址
        add_header Access-Control-Allow-Origin *;
        add_header Access-Control-Allow-Methods "GET, POST, OPTIONS";
        add_header Access-Control-Allow-Headers "Content-Type, Authorization";
        add_header Access-Control-Allow-Credentials true;
    }
}

适用场景

  • 前端和后端通过同一域名访问。
  • 使用 Nginx 进行跨域代理时性能更高。

总结

Spring Boot 提供了多种跨域解决方案,可根据需求选择合适的方式:

  1. 注解方式:简单场景适用。
  2. 全局配置:推荐用于大多数项目。
  3. 自定义过滤器:适用于复杂需求。
  4. Nginx 配置:在后端无控制权时使用。

通过这些方法,可以高效、安全地解决前后端分离开发中的跨域问题。