什么是跨域?怎么解决跨域问题

481 阅读6分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

一、同源策略

同源策略SOP(Same origin policy),是由 Netscape 提出的一个安全策略,它是浏览器最核心也是最基本的安全功能,如果缺少同源策略,则浏览器的正常功能可能都会受到影响,现在所有支持JavaScript的浏览器都会使用这个策略。可以说Web是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现。同源策略会阻止一个域的javascript脚本和另外一个域的内容进行交互。所谓同源(即指在同一个域)就是两个页面具有相同的协议(protocol),主机(host)和端口号(port)

规定:浏览器要求,在解析Ajax请求时,要求浏览器的路径与Ajax的请求的路径必须满足三个要求,则满足同源策略,可以访问服务器。

前提条件:

  1. 浏览器的请求路径
  2. Ajax请求的网址

要求:

协议、域名、端口号都相同,只要有一个不相同,那么都是非同源

image.png

进一步细分下的要求: 当协议、子域名、主域名、端口号中任意一个不相同时,都算作不同域。不同域之间相互请求资源,就算作“跨域”。

image.png

二、同源策略案例

当一个请求url的协议、域名、端口三者之间任意一个与当前页面url不同即为跨域。

浏览器地址Ajax请求地址是否跨域原因
www.a.com:8080/a.jswww.a.com:8080/b.js同源(协议、域名、端口号相同)
www.a.com:80/a.jswww.a.com/b.jshttp协议,默认端口为80
www.a.com/a.jswww.a.com:443/b.jshttps协议默认端口为443
www.a.com:8080/a.jswww.a.com:8080/a.js跨域协议不同
www.a.com:8080/a.jswww.b.com:8080/a.js跨域主域名不同
www.a.com:8080/a.jsblog.a.com:8080/a.js跨域子域名不同
www.a.com:8080/a.jswww.a.com:8090/a.js跨域端口号不同
www.baidu.com/https://39.156.66.14/跨域前提: IP与域名映射域名不同

三、什么是跨域

CORS全称Cross-Origin Resource Sharing,意为跨域资源共享。当一个资源去访问另一个不同域名或者同域名不同端口的资源时,就会发出跨域请求。如果此时另一个资源不允许其进行跨域资源访问,那么访问就会遇到跨域问题。

跨域指的是浏览器不能执行其它网站的脚本。是由浏览器的同源策略造成的,是浏览器对JavaScript 施加的安全限制。

有一点必须要注意:跨域并不是请求发不出去,请求能发出去,服务端能收到请求并正常返回结果,只是结果被浏览器拦截了。之所以会跨域,是因为受到了同源策略的限制,同源策略要求源相同才能正常进行通信,即协议、域名、端口号都完全一致。

同源策略限制从一个源加载的文档或脚本如何与来自另一个源的资源进行交互。这是一个用于隔离潜在恶意文件的关键的安全机制。它的存在可以保护用户隐私信息,防止身份伪造等(读取Cookie)。

同源策略限制内容有:

  • Cookie、LocalStorage、IndexedDB 等存储性内容
  • DOM 节点
  • AJAX 请求不能发送

但是有三个标签允许跨域加载资源:

<img src=XXX> 
<link href=XXX> 
<script src=XXX>

四、跨域解决方案

1.ajax的jsonp

  • 概念

JSONP(JSON with Padding)是JSON的一种“使用模式”,可用于解决主流浏览器的跨域数据访问的问题。 由于同源策略,一般来说位于www.example.com的网页无法与不是www.example.com的服务器沟通,而HTML的返回值语法固定的:callback(JSON数据)

  • JSONP优缺点

JSONP优点是兼容性好,可用于解决主流浏览器的跨域数据访问的问题。缺点是仅支持get方法具有局限性。

  • jQuery的ajax解决方案
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8"/>
    <title>Page Index</title>
</head>
<body>
<h2>前台系统</h2>
<p id="info"></p>
</body>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.js"></script>
<script>
    $.ajax({
        type: 'get', //jsonp默认为get请求,即使写post也会转换成get方式
        url: 'http://localhost:8080/hello', // 服务端地址
        async: false, // jsonp默认为false,即使写true也会转换成false
        dataType: 'jsonp', // jsonp调用固定写法
        jsonp: 'callback', // 传递给请求处理程序或页面的,用以获得jsonp回调函数名的参数名(可不写,默认为:callback)。即:?callback=xxx中的callback部分
        jsonpCallback:"jsonpCallback", //自定义的jsonp回调函数名称,默认为jQuery自动生成的随机函数名,也可以写"?",jQuery会自动为你处理数据。即:?callback=xxx中的xxx部分
        data: {a: "a"}, // 传递的具体参数
        success: function (result) {
            $("#info").html(result);
        },error: function(){ // 调用失败之后的方法
            alert('error');
        }
    })
</script>
</html>

后端部分

@GetMapping("/hello")
public String hello(HttpServletRequest request) {
    List list= new ArrayList();
    list.add(request.getParameter("a"));
    list.add("1");
    String callback = request.getParameter("callback");
    String s = JSON.toJSONString(list);
    String data = callback + "(" + s + ")";
    return data;
}

遇到的问题返回集合时,字段为String,不能自动加上"",导致前端不能识别为字符串使用阿里json解决

String s = JSON.toJSONString(list);

2.CORS方式

说明:CORS(Cross-origin resource sharing) “跨域资源共享”,现在的主流的浏览器都支持cors的方式.。如果需要跨域,则需要配置响应头信息,标识是否允许。

在响应头上添加Access-Control-Allow-Origin属性,指定同源策略的地址。同源策略默认地址是网页的本身。只要浏览器检测到响应头带上了CORS,并且允许的源包括了本网站,那么就不会拦截请求响应。

1.CORS原理

整个CORS通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。因此,实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信。

2.CORS优缺点

CORS要求浏览器(>IE10)和服务器的同时支持,是跨域的根本解决方法,由浏览器自动完成。 优点在于功能更加强大支持各种HTTP Method,缺点是兼容性不如JSONP。

对于 CORS的跨域请求,主要有以下几种方式可供选择:

  1. 使用注解 @CrossOrigin
  2. 返回新的CorsFilter
  3. 重写 WebMvcConfigurer
  4. 自定web filter 实现跨域
  5. 手动设置响应头 (HttpServletResponse)

方法一:@CrossOrigin注解方法

@CrossOrigin
//@CrossOrigin(value = "http://localhost:8080")  //单独作用在url上
@GetMapping("/Axios")
public String axios(){
    return "hello,world";
}

方法二:添加CORS过滤器

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 CorsConfig {
    @Bean
    public CorsFilter corsFilter() {
        //1. 添加 CORS配置信息
        CorsConfiguration config = new CorsConfiguration();
        //放行哪些原始域
        config.addAllowedOrigin("*");
        //是否发送 Cookie

        //config.setAllowCredentials(true);
        //放行哪些请求方式
        config.addAllowedMethod("*");
        //放行哪些原始请求头部信息
        config.addAllowedHeader("*");
        //暴露哪些头部信息
        config.addExposedHeader("*");
        //2. 添加映射路径
        UrlBasedCorsConfigurationSource corsConfigurationSource = new UrlBasedCorsConfigurationSource();
        corsConfigurationSource.registerCorsConfiguration("/**",config);
        //3. 返回新的CorsFilter
        return new CorsFilter(corsConfigurationSource);
    }
    //或者下面写法
    @Bean
    public WebMvcConfigurer MyWebMvcConfigurer() {
        return new WebMvcConfigurer() {
            @Override
            public void addCorsMappings(CorsRegistry registry) {
                registry.addMapping("/**")
                        //是否发送Cookie
                        //.allowCredentials(true)
                        //放行哪些原始域
                        .allowedOrigins("*")
                        .allowedMethods(new String[]{"GET", "POST", "PUT", "DELETE"})
                        .allowedHeaders("*")
                        .exposedHeaders("*");
            }
        };
    }
}

方法三:实现接口方式

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 CorsConfiguration implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")//设置映射
                .allowedOriginPatterns("*")//允许哪些域访问
                .allowedMethods("GET","POST","DELETE","PUT","OPTIONS","HEAD")//允许哪些方法
                .allowCredentials(true)//是否允许携带cookie
                .maxAge(60)//设置60s不需要重复访问
                .allowedHeaders("*");//允许哪些头信息
    }
}

方法四:添加过滤器

import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Component
public class CrosFiter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        Filter.super.init(filterConfig);
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        response.setHeader("Access-Control-Allow-Origin", "*");
        response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
        response.setHeader("Access-Control-Max-Age", "3600");
        response.setHeader("Access-Control-Allow-Headers", "x-requested-with,content-type");
        filterChain.doFilter(servletRequest, servletResponse);
    }

    @Override
    public void destroy() {
        Filter.super.destroy();
    }
}

方法五:手动设置响应头(局部跨域)

使用 HttpServletResponse 对象添加响应头(Access-Control-Allow-Origin)来授权原始域,这里 Origin的值也可以设置为 “*”,表示全部放行。

@RequestMapping("/index")
public String index(HttpServletResponse response) {
    response.addHeader("Access-Control-Allow-Origin","*");
    return "index";
}