跨域是什么?多种方法解决跨域问题

跨域是什么?如何解决跨域

跨域是什么

名词解释:

同源策略(Same-Origin Policy,简称 SOP)是浏览器的一种重要安全机制,用于防止一个来源的恶意脚本访问另一个来源的敏感资源。这个策略由 Netscape 公司在 1995 年引入,现已成为所有现代浏览器的基础安全功能。

什么是“同源”

一个 URL 的“源”由协议(scheme)、主机(host)和端口(port)三个部分组成。只有当两个 URL 的这三个部分完全相同时,它们才被认为是同源的。

  • 协议:例如 HTTP、HTTPS。
  • 主机:例如 www.example.com。
  • 端口:例如 80、443。

同源策略的限制

同源策略主要限制了以下几种行为:

  • Cookie、LocalStorage 和 IndexDB 等存储内容的读取。
  • DOM 和 JavaScript 对象的访问。
  • AJAX 请求的发送和接收。

产生跨域问题的主要原因

  1. 不满足同源策略跨域问题是在Web开发中常见的一个问题,通常在浏览器访问一个与当前页面不同的域名、协议或端口的资源时产生。这是因为浏览器出于安全考虑,默认采用同源策略(Same-Origin Policy),限制了从一个源加载的脚本如何能够与来自另一个源的资源进行交互。

    1. 不同的域名

      例如,从 http://example.com 访问 http://another-domain.com 的资源。

    2. 不同的子域名

      例如,从 http://sub.example.com 访问 http://example.com 的资源。

    3. 不同的协议

      例如,从 http://example.com 访问 https://example.com 的资源。

    4. 不同的端口

      例如,从 http://example.com:8080 访问 http://example.com:3000 的资源。

  2. 多次添加 cors头部

    有的小伙伴说我加了允许跨域 ,为什么还是会报跨域错误呢?

    原因是多个地方添加了允许跨域的header 导致响应头中有多个Access-Control-Allow-Origin 这时候也会报跨域错误。

响应头中多个允许跨域头部

如何解决跨域

对于第一种跨域问题(违反同源策略)

1.nginx
  1. 前端访问后端服务

    将请求发送只同一地址和端口 让nginx反向代理 就不违反同源策略

    server {
        listen 80;
    
        server_name frontend.com;
    
        location / {
            root /path/to/your/frontend;
            try_files $uri $uri/ /index.html;
        }
    	# 其实这时已经符合同源策略 不需要加上允许跨域的请求头了 所以下方 proxy_set_header 都可加可不加
        location /api {
        	# 这里不想要转发/api 可以用rewrite 去掉/api
            proxy_pass http://backend.com/api;
            # 将原始请求的主机名传递给后端服务器。如果后端服务器需要知道原始的主机名,这条指令是有用的。
            proxy_set_header Host $host;
            # 将客户端的真实 IP 地址传递给后端服务器。在日志记录和 IP 限制等情况下是有用的。
            proxy_set_header X-Real-IP $remote_addr;
            # 用于传递客户端的原始IP地址 $proxy_add_x_forwarded_for包含了客户端的IP地址和经过的代理服务器的IP地址。 
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            # 传递原始请求的协议(HTTP 或 HTTPS)。这在后端服务器需要知道请求的原始协议时是有用的。
            proxy_set_header X-Forwarded-Proto $scheme;
        }
    }
    
  2. 访问第三方服务

    有时我们的服务需要访问第三方服务 这时也可能会报跨域 这时我们要注意 如果第三方接口允许了跨域 我们再自己加上允许跨域 就会导致第二种跨域报错

    server {
        listen 80;
        server_name yourdomain.com;
    
        location /api {
            proxy_pass http://external-api.com/api;
    
            # 处理 OPTIONS 预检请求
            if ($request_method = 'OPTIONS') {
            	# 添加 CORS 头部
            	# 允许任何来源(域)访问资源。
                add_header 'Access-Control-Allow-Origin' '*';
                # 指定允许的 HTTP 请求方法。
                add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE';
                # 指定允许的 HTTP 请求头部字段。
                add_header 'Access-Control-Allow-Headers' 'Origin, Content-Type, Accept, Authorization, X-Custom-Header';
                # 指定是否允许发送凭据(如 Cookie 和 HTTP 认证信息)。
                add_header 'Access-Control-Allow-Credentials' 'true';
                return 204;  # No Content
            }
        }
    }
    
    
2.springboot配置cors
  1. 使用springMVC的WebMvcConfigurer
    @Configuration
    public class CorsConfig implements WebMvcConfigurer {
    
        @Override
        public void addCorsMappings(CorsRegistry registry) {
            registry.addMapping("/**")
                    .allowedOrigins("*")  // 可以指定具体的域名,替换 "*" 为 "http://example.com"
                    .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
                    .allowedHeaders("*")
                    .allowCredentials(true);
        }
    }
    
    
  2. 使用spring的@CrossOrigin注解

    可以加载类 或者方法上

    @RestController
    @RequestMapping("/api")
    public class ApiController {
        // 可直接加在类上
        @CrossOrigin(origins = "*")  // 可以指定具体的域名,替换 "*" 为 "http://example.com"
        @GetMapping("/data")
        public String getData() {
            return "Data from server";
        }
    }
    
3.使用Servlet 过滤器
  1. 自定义过滤器继承Filter
    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 {
            HttpServletResponse httpResponse = (HttpServletResponse) response;
            httpResponse.setHeader("Access-Control-Allow-Origin", "*");  // 可以指定具体的域名,替换 "*" 为 "http://example.com"
            httpResponse.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
            httpResponse.setHeader("Access-Control-Allow-Headers", "Origin, Content-Type, Accept, Authorization");
            httpResponse.setHeader("Access-Control-Allow-Credentials", "true");
    
            // 对于预检请求,直接返回状态码 204
            if ("OPTIONS".equalsIgnoreCase(((HttpServletRequest) request).getMethod())) {
                httpResponse.setStatus(HttpServletResponse.SC_NO_CONTENT);
                return;
            }
            chain.doFilter(request, response);
        }
    
        @Override
        public void destroy() {
        }
    }
    
    
  2. 注册自定义过滤器
    @Configuration
    public class FilterConfig {
    
        @Bean
        public FilterRegistrationBean<CorsFilter> corsFilter() {
            FilterRegistrationBean<CorsFilter> registrationBean = new FilterRegistrationBean<>();
            registrationBean.setFilter(new CorsFilter());
            registrationBean.addUrlPatterns("/*");
            registrationBean.setOrder(0);
            return registrationBean;
        }
    }
    
4.Spring Security 配置 CORS

如果项目中使用了Spring Security 。可在代码中添加

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.cors().and().csrf().disable();
    }

    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        // 可以指定具体的域名,替换 "*" 为 "http://example.com"
        configuration.setAllowedOrigins(Arrays.asList("*"));  
        configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS"));
        configuration.setAllowedHeaders(Arrays.asList("Origin", "Content-Type", "Accept", "Authorization"));
        configuration.setAllowCredentials(true);

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }
}
5.使用 Spring 拦截器配置 CORS

拦截器适用于需要在请求处理前后进行拦截和处理的场景。可以通过实现 HandlerInterceptor 接口来配置 CORS。

需要注意

拦截器默认不拦截静态资源请求 如果需要也可以配置,但是会带来不必要的性能开销

  1. 自定义CORS拦截器

    @Component
    public class CorsInterceptor implements HandlerInterceptor {
    
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            response.setHeader("Access-Control-Allow-Origin", "*");
            response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
            response.setHeader("Access-Control-Allow-Headers", "Origin, Content-Type, Accept, Authorization");
            response.setHeader("Access-Control-Allow-Credentials", "true");
            // 对于预检请求,直接返回状态码 204
            if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
                response.setStatus(HttpServletResponse.SC_NO_CONTENT);
                return false;
            }
            return true;
        }
    }
    
  2. 注册,指定拦截路径

    @Configuration
    public class WebConfig implements WebMvcConfigurer {
    
        @Autowired
        private CustomInterceptor customInterceptor;
    
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            registry.addInterceptor(customInterceptor).addPathPatterns("/**");  // 拦截所有路径
        }
    }
    
6.使用 Spring Cloud Gateway 配置 CORS

如果项目使用了微服务架构。用到了网关gateway

  1. 配置 CORS 过滤器

    本质还是配置过滤器

    @Configuration
    public class CorsConfig {
    
        @Bean
        public CorsWebFilter corsWebFilter() {
            UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
            CorsConfiguration config = new CorsConfiguration();
            // 设置允许哪些url访问跨域资源,*表示全部允许
            config.addAllowedOrigin("*");
            // 允许访问的头信息,*表示全部
            config.addAllowedHeader("*");
            // 允许所有请求方法(GET,POST等)访问跨域资源
            config.addAllowedMethod("*");
            config.setAllowCredentials(Boolean.TRUE);
    
            source.registerCorsConfiguration("/**", config);
            return new CorsWebFilter(source);
        }
    }
    
  2. 配置文件配置

    更加灵活

    spring:
      cloud:
        gateway:
          globalcors:
            corsConfigurations:
              '[/**]':
                allowedOrigins: "*"
                allowedMethods:
                  - GET
                  - POST
                  - PUT
                  - DELETE
                  - OPTIONS
                allowedHeaders: "*"
                allowCredentials: true
    
  3. gateway局部过滤器

    这种方式更加灵活 可以对部分代理的接口单独添加跨域

    spring:
      cloud:
        gateway:
          routes:
            - id: add_cors_headers_route
            # 目标地址
              uri: https://example.org
              predicates:
                - Path=/api/**
              filters:
                - name: AddResponseHeader
                  args:
                    Access-Control-Allow-Origin: "*"
                    Access-Control-Allow-Methods: "GET,POST,PUT,DELETE,OPTIONS"
                    Access-Control-Allow-Headers: "Origin,Content-Type,Accept,Authorization"
                    Access-Control-Allow-Credentials: "true"
    

对与第二种跨域问题(请求头中重复添加Access-Control-Allow-Origin)

多个地方添加了允许跨域的请求头

如果是自己的服务可以通过排查整体请求链路

  • 查看整个请求的链路 去掉多余添加请求头的地方,保证保留一个。
如果是调用第三方接口,或者说不想去排查服务
  1. gateway的DedupeResponseHeader

    DedupeResponseHeader 剔除重复的响应头 保留一个,接受一个name参数和一个可选strategy参数。name可以包含以空格分隔的标题名称列表。

    spring:
      cloud:
        gateway:
          routes:
            - id: dedupe_response_header_route
              uri: https://example.org
     	      filters:
     	        - name: DedupeResponseHeader
     	          args:
     	            name: Access-Control-Allow-Credentials Access-Control-Allow-Origin
     	            strategy: RETAIN_FIRST
    
  2. nginx的proxy_hide_header
    location / {
        proxy_pass http://example.org;
    
        # 删除重复的响应头
        proxy_hide_header Access-Control-Allow-Credentials;
        proxy_hide_header Access-Control-Allow-Origin;
    }
    
  3. 使用filter自定义逻辑
    @WebFilter("/api/*")
    public class RemoveDuplicateCorsHeadersFilter implements Filter {
    
        private static final List<String> CORS_HEADERS = Arrays.asList(
                "Access-Control-Allow-Origin",
                "Access-Control-Allow-Credentials"
        );
    
        @Override
        public void init(FilterConfig filterConfig) throws ServletException {
            // 初始化逻辑,可选实现
        }
    
        @Override
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
                throws IOException, ServletException {
            HttpServletRequest httpRequest = (HttpServletRequest) request;
            Map<String, List<String>> headerMap = new HashMap<>();
    
            // 收集请求头
            Enumeration<String> headerNames = httpRequest.getHeaderNames();
            while (headerNames.hasMoreElements()) {
                String headerName = headerNames.nextElement();
                Enumeration<String> headers = httpRequest.getHeaders(headerName);
                List<String> headerValues = Collections.list(headers);
                headerMap.put(headerName, headerValues);
            }
    
            // 移除重复的 CORS 相关请求头
            removeDuplicateCorsHeaders(headerMap);
    
            // 应用修改后的请求头
            applyModifiedHeaders(httpRequest, headerMap);
    
            // 继续请求链
            chain.doFilter(httpRequest, response);
        }
    
        private void removeDuplicateCorsHeaders(Map<String, List<String>> headerMap) {
         
            // 遍历请求头,找出重复的 CORS 相关请求头
            for (String headerName : CORS_HEADERS) {
                if (headerMap.containsKey(headerName)) {
                     List<String> values = headerMap.get(headerName);
                    if (values != null && values.size() > 1) {
                        // 有两个值 只保留一个
                        List<String> newValues = new ArrayList<String>();
                        newValues.add(values.get(0));
                        headerMap.put(headerName,newValues);
                    } 
                }
            }
        }
        private void applyModifiedHeaders(HttpServletRequest request, Map<String, List<String>> headerMap) {
            // 移除需要移除的请求头
            for (String headerToRemove : CORS_HEADERS) {
                request.removeHeader(headerToRemove);
            }
            // 将修改后的请求头重新设置到请求对象中
            headerMap.forEach((headerName, headerValues) -> {
                for (String headerValue : headerValues) {
                    request.addHeader(headerName, headerValue);
                }
            });
        }
        @Override
        public void destroy() {
            // 销毁逻辑,可选实现
        }
    }
    
跨域是指在浏览器中,一个网页的脚本试图访问不同源(域、协议、端口)的资源时产生的安全限制。浏览器为了保护用户信息安全,会阻止跨域请求。 解决跨域问题多种方法,以下是一些常见的解决方案: 1. JSONP(仅支持GET请求):通过在页面中动态创建`<script>`标签,向目标服务器请求数据。服务器返回JSONP格式的数据,浏览器解析并执行回调函数。 2. CORS(跨域资源共享):服务端设置响应头部,允许指定的源进行跨域请求。在Spring Boot中,可以通过添加`@CrossOrigin`注解来实现。 3. 代理服务器:配置一个同源的代理服务器,将浏览器的请求转发到目标服务器,然后将响应返回给浏览器。这种方式需要在服务器端进行配置。 4. WebSocket:使用WebSocket协议进行通信,WebSocket不受同源策略限制。 对于Spring Boot应用解决跨域问题,可以使用CORS方式。在控制器类或方法上添加`@CrossOrigin`注解即可实现跨域请求的支持。例如: ```java @RestController @CrossOrigin(origins = "http://example.com") public class MyController { // ... } ``` 上述代码表示允许来自"http://example.com"域的跨域请求。你可以根据实际情况修改`origins`参数,甚至使用`"*"`来允许所有来源的请求。 同时,你也可以在全局配置文件(如`application.properties`或`application.yml`)中添加以下配置来实现全局跨域支持: ```yaml spring: cors: allowed-origins: http://example.com ``` 这样配置后,所有的请求都会允许来自"http://example.com"域的跨域请求。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值