跨域超详解

一、为什么存在跨域问题

浏览器不允许跨域请求是因为跨域请求可能引发安全风险。同源策略(Same-Origin Policy)是一种浏览器安全机制,它要求网页中执行的脚本只能与同一源(协议、域名和端口相同)下的资源进行交互。
同源策略有助于防止恶意网站窃取用户的敏感信息或进行其他安全攻击。如果浏览器允许跨域请求,那么恶意网站就可能通过在用户浏览器中执行脚本来访问其他域名下的敏感数据,例如用户登录凭证、私密文件等。
虽然跨域请求受到同源策略的限制,但浏览器提供了一些机制来支持安全的跨域通信,如跨域资源共享(CORS)和 JSONP(JSON with Padding)等。通过这些机制,服务器可以明确告知浏览器哪些跨域请求是安全可信的,并授权浏览器访问相关资源。
尽管存在跨域请求的限制,但同源策略对于保护用户和维护网络安全至关重要。开发人员可以使用服务器端的代理或通过在服务器上配置 CORS 等方式来解决跨域问题。这样可以确保跨域请求只在受信任的环境下进行,并减少潜在的安全风险。

二、什么是跨域

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

当前页面url被请求页面url是否跨域原因
http://www.test.com/http://www.test.com/index.html同源(协议、域名、端口号相同)
http://www.test.com/https://www.test.com/index.html跨域协议不同(http/https)
http://www.test.com/http://www.baidu.com/跨域主域名不同(test/baidu)
http://www.test.com/http://blog.test.com/跨域子域名不同(www/blog)
http://www.test.com:8080/http://www.test.com:7001/跨域端口号不同(8080/7001)

三、如何解决跨域

3.1 CORS(重要)

CORS(Cross-Origin Resource Sharing,跨域资源共享)是一个 W3C 标准,定义了在必须访问跨域资源时,浏览器与服务器应该如何沟通。CORS 背后的基本思想,就是使用自定义的 HTTP 头部让浏览器与服务器进行沟通,从而决定请求或响应是应该成功,还是应该失败。
CORS 需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能,IE 浏览器不能低于 IE10。
整个 CORS 通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS 通信与同源的 AJAX 通信没有差别,代码完全一样。浏览器一旦发现 AJAX 请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。
因此,实现 CORS 通信的关键是服务器。只要服务器实现了 CORS 接口,就可以跨源通信。
其实 CORS 很好理解,服务端在 HTTP 响应中添加一个 Header Access-Control-Allow-Origin:http://www.masikkk.com header 值是允许的源,浏览器看到自己的源和服务端返回的能匹配上,就允许跨域。
浏览器将 CORS 请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)。

3.1.1 简单请求和非简单请求

(1) 请求方法是以下三种方法之一:

  • HEAD
  • GET
  • POST

(2)HTTP的头信息不超出以下几种字段:

  • Accept
  • Accept-Language
  • Content-Language
  • Last-Event-ID
  • Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain

凡是不同时满足上面两个条件,就属于非简单请求。

3.1.2 跨域主要涉及4个响应头

  • Access-Control-Allow-Origin 用于设置允许跨域请求源地址 (预检请求和正式请求在跨域时候都会验证)
  • Access-Control-Allow-Headers 跨域允许携带的特殊头信息字段 (只在预检请求验证)
  • Access-Control-Allow-Methods 跨域允许的请求方法或者说HTTP动词 (只在预检请求验证)
  • Access-Control-Allow-Credentials 是否允许跨域使用cookies,如果要跨域使用cookies,可以添加上此请求响应头,值设为true(设置或者不设置,都不会影响请求发送,只会影响在跨域时候是否要携带cookies,但是如果设置,预检请求和正式请求都需要设置)。不过不建议跨域使用(项目中用到过,不过不稳定,有些浏览器带不过去),除非必要,因为有很多方案可以代替。

3.1.3 简单请求 CORS 处理过程

对于简单的跨域请求,浏览器会自动在请求的头信息加上 Origin 字段,表示本次请求来自哪个源(协议 + 域名 + 端口),服务端应该获取到这个值,然后判断是否同意这次请求并返回。

  1. 简单请求处理过程:
    浏览器自动在跨域请求中附加一个的 Origin 头部,其中包含请求页面的源信息(协议、域名和端口),以便服务器根据这个头部信息来决定是否给予响应。例如:Origin: http://www.masikkk.com
  2. 如果服务器认为这个请求可以接受,就在 http 响应中添加 header Access-Control-Allow-Origin,其值设为和请求中 Origin 相同的源信息(如果是公共资源,可以设置 * 表示接受任意域名的请求 )。例如:Access-Control-Allow-Origin:http://www.masikkk.com
  3. 没有这个头部或者有这个头部但源信息不匹配,浏览器就会驳回请求。注意,请求和响应都不包含 cookie 信息。
  4. 如果需要包含 cookie 信息,ajax 请求需要设置 XMLHttpRequest 的属性 withCredentials 为 true,服务器需要设置响应头部 Access-Control-Allow-Credentials: true

3.1.4 复杂请求 CORS 处理过程(先发 OPTION 请求)

非简单请求是那种对服务器有特殊要求的请求,比如请求方法是 PUT 或 DELETE,或者 Content-Type 字段的类型是 application/json。
复杂请求的 CORS 请求,会在正式通信之前,增加一次 HTTP 查询请求,称为 预检请求(preflight),预检请求使用的 http 方法 OPTIONS**,通过该请求来知道服务端是否允许跨域请求。
3395727666-7909e09b79e67694.png
复杂请求处理过程:

  1. 浏览器发送预检请求

浏览器在发送真正的请求之前,先发送一个 Preflight 预检请求给服务器,这种请求使用 OPTIONS 方法,调用的也是同一个 API,发送下列头部:
Origin 必须,与简单的请求相同,值为请求页面的源信息(协议、域名和端口)
Access-Control-Request-Method 必须,请求自身使用的 http 方法,例如 PUT。
Access-Control-Request-Headers 可选,自定义的头部信息,多个头部以逗号分隔。

OPTIONS /cors HTTP/1.1
Origin: https://masikkk.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: X-Custom-Header
  1. 服务端返回预检响应

收到这个请求后,服务器可以决定是否允许这种类型的请求。服务器通过在响应中发送如下头部与浏览器进行沟通:
Access-Control-Allow-Origin 必须,与简单的请求相同,允许访问的源,接受任意域就返回 *
Access-Control-Allow-Methods 必须,允许的方法,多个方法以逗号分隔。表明服务器支持的所有跨域请求的方法。注意,返回的是所有支持的方法,而不单是浏览器请求的那个方法。这是为了避免多次”预检”请求。
Access-Control-Allow-Headers 如果浏览器请求包括 Access-Control-Request-Headers 字段,则 Access-Control-Allow-Headers 字段是必需的。它也是一个逗号分隔的字符串,表明服务器支持的所有头信息字段,不限于浏览器在”预检”中请求的字段。
Access-Control-Max-Age 可选,用来指定本次预检请求的有效期,单位为秒。或者说应该将这个 Preflight 请求缓存多长时间。
预检请求响应示例:

Access-Control-Allow-Origin: http://devgou.com
Access-Control-Allow-Methods: GET, POST
Access-Control-Allow-Headers: NCZ
Access-Control-Max-Age: 1728000
  1. 浏览器的正常请求和回应

一旦服务器通过了”预检”请求,以后每次浏览器正常的CORS请求,就都跟简单请求一样,会有一个 Origin 头信息字段。服务器的回应,也都会有一个 Access-Control-Allow-Origin 头信息字段。

3.1.5 SpringBoot框架实现CORS

1. CorsFilter过滤器实现全局配置
@Slf4j
@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", "*");
        response.setHeader("Access-Control-Allow-Methods", "POST, PUT, GET, OPTIONS, DELETE");
        response.setHeader("Access-Control-Max-Age", "3600");
        response.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, client_id, uuid, Authorization");
        response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
        response.setHeader("Pragma", "no-cache");
        filterChain.doFilter(servletRequest,response);
    }
}
2. 重写WebMvcConfigurer的addCorsMappings 方法实现全局配置
@Configuration
public class CorsConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")//项目中的所有接口都支持跨域
                .allowedOrigins("*")//所有地址都可以访问,也可以配置具体地址
                .allowCredentials(true)
                .allowedMethods("*")//"GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS"
                .maxAge(3600);// 跨域允许时间
    }
}
3. @CrossOrigin注解局部配置

直接在方法或类上增加@CrossOrigin解决跨域

/**
 * Created with IDEA
 *
 * @Description xxxx控制层
 * @Version 1.0
 */
@RestController
@CrossOrigin
@RequestMapping("/situation")
public class SituationController extends PublicUtilController {
 
    @Autowired
    private SituationService situationService;
    // log日志信息
    private static Logger LOGGER = Logger.getLogger(SituationController.class); 
}

3.2 Nginx反向代理(重要)

使用 nginx 反向代理实现跨域,是最简单的跨域方式。只需要修改 nginx 的配置即可解决跨域问题,支持所有浏览器,支持 session,不需要修改任何代码,并且不会影响服务器性能。
我们只需要配置nginx,在一个服务器上配置多个前缀来转发http/https请求到多个真实的服务器即可。这样,这个服务器上所有url都是相同的域 名、协议和端口。因此,对于浏览器来说,这些url都是同源的,没有跨域限制。而实际上,这些url实际上由物理服务器提供服务。这些服务器内的 javascript可以跨域调用所有这些服务器上的url。
具体操作:
用nginx做请求转发
需在 nginx 中配置的几个参数:

Access-Control-Allow-Origin:必含,允许的域名,只能填通配符或者单域名
Access-Control-Allow-Methods:必含,允许跨域请求的 http 方法
Access-Control-Allow-Headers:返回支持的 http 请求头
Access-Control-Allow-Credentials:可选,标志着当前请求是否包含 cookies 信息,布尔值。只有一个可选值:true,如果不包含 cookies,请略去该项,而不是填 false。这一项与XmlHttpRequest 对象当中的 withCredentials 属性应保持一致,即 withCredentials 为true时该项也为 true;withCredentials为false时,省略该项不写。反之则导致请求失败。
Access-Control-Max-Age:可选,以秒为单位的缓存时间,用于缓存预检请求。
http {
  ......
  add_header Access-Control-Allow-Origin *;
  add_header Access-Control-Allow-Headers X-Requested-With;
  add_header Access-Control-Allow-Methods GET,POST,OPTIONS;
  ......
}
server {
	listen       80;
	server_name  xxx.com;
 
	location /xxx-web/papi {
		add_header 'Access-Control-Allow-Origin' $http_origin;
		add_header 'Access-Control-Allow-Credentials' 'true';
		add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
		add_header 'Access-Control-Allow-Headers' 'DNT,web-token,app-token,Authorization,Accept,Origin,Keep-Alive,User-Agent,X-Mx-ReqToken,X-Data-Type,X-Auth-Token,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
		add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
		if ($request_method = 'OPTIONS') {
			add_header 'Access-Control-Max-Age' 1728000;
			add_header 'Content-Type' 'text/plain; charset=utf-8';
			add_header 'Content-Length' 0;
			return 204;
		}
		root   html;
		index  index.html index.htm;
		proxy_pass http://127.0.0.1:7071;
		proxy_set_header Host $host;
		proxy_set_header X-Real-IP $remote_addr;
		proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
		proxy_set_header X-Forwarded-Proto $scheme;
		proxy_connect_timeout 5;
	}
 
	location /xxx-web {
		add_header 'Access-Control-Allow-Origin' $http_origin;
		add_header 'Access-Control-Allow-Credentials' 'true';
		add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
		add_header 'Access-Control-Allow-Headers' 'DNT,web-token,app-token,Authorization,Accept,Origin,Keep-Alive,User-Agent,X-Mx-ReqToken,X-Data-Type,X-Auth-Token,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
		add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
		if ($request_method = 'OPTIONS') {
			add_header 'Access-Control-Max-Age' 1728000;
			add_header 'Content-Type' 'text/plain; charset=utf-8';
			add_header 'Content-Length' 0;
			return 204;
		}
		root   html;
		index  index.html index.htm;
		proxy_pass http://127.0.0.1:8080;
		proxy_set_header Host $host;
		proxy_set_header X-Real-IP $remote_addr;
		proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
		proxy_set_header X-Forwarded-Proto $scheme;
		proxy_connect_timeout 5;
	}
 
	location / {
		root   /var/www/xxx/wechat/webroot;
		index  index.html index.htm;
	}
 
	error_page   500 502 503 504  /50x.html;
	location = /50x.html {
		root   html;
	}
}

这两段配置的区别在于跨域设置的粒度和作用范围。 第一段配置在 “http” 块中设置了全局的跨域配置,使用了通配符
“*”,表示允许所有来源的请求进行跨域访问。它设置了响应头中的
“Access-Control-Allow-Origin”、“Access-Control-Allow-Headers” 和
“Access-Control-Allow-Methods”
字段,用于指定允许跨域访问的域名、请求头和请求方法。这样的设置会对整个服务器中的所有请求生效,包括所有的 “location” 块。
第二段配置则是在特定的 “location” 块中设置了跨域配置。它只对以 “/api” 开头的请求路径生效。这段配置设置了响应头中的
“Access-Control-Allow-Origin”、“Access-Control-Allow-Headers” 和
“Access-Control-Allow-Methods”
字段,并且针对具体的请求路径设置了更详细的允许的域名、请求头和请求方法。对于其他请求路径,比如根路径 “/”,将不会应用这些跨域配置。
所以,第一段配置是全局的跨域配置,适用于整个服务器,而第二段配置是特定路径下的跨域配置,仅适用于 “/api” 路径。
一般来说,如果你只需要简单地允许所有来源的请求进行跨域访问,可以使用第一段配置。如果你需要针对特定的接口或路径设置更详细的跨域策略,可以使用第二段配置。具体要根据你的需求和应用场景来选择配置方式。

如果只响应简单请求,且不使用cookie,只设置Access-Control-Allow-Origin为前端的域名即可
比如只允许 http://devgou.com 访问,可如下配置:

add_header 'Access-Control-Allow-Origin' 'http://devgou.com';

如果需要允许来自任何域的访问,可以这样配置

add_header Access-Control-Allow-Origin *;

如果不想允许所有,但是又需要允许多个域名,那么就需要用到 nginx 的 map 指令
比如想允许如下 map 中的四个域名访问:

http {
  map $http_origin $corsHost {
    default 0;
    "~http://madaimeng.com" http://madaimeng.com;
    "~http://devgou.com" http://devgou.com;
    "~http://masikkk.com" http://masikkk.com;
    "~http://localhost:4000" http://localhost:4000;
  }

  server {
    listen       80;
    server_name  api.masikkk.com api.madaimeng.com api.devgou.com;

    location / {
      add_header 'Access-Control-Allow-Origin' $corsHost;
      proxy_pass http://localhost:8001;
    }
  }
}

总结:

  • 同源策略是浏览器需要遵循的标准,而如果是服务器向服务器请求就无需遵循同源
  • CORS支持所有类型的HTTP请求,是跨域HTTP请求的根本解决方案
  • 日常工作中,用得比较多的跨域方案是cors和nginx反向代理
  • 跨域一句话,浏览器为了安全做的限制,需要服务端做适配。

参考:

  1. https://www.jianshu.com/p/cedc8b1cd84c
  2. https://www.qiufeng.blue/frontend/cors.html
  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
随着Web应用程序的不断发展,越来越多的Web开发者需要处理跨域访问的问题。尤其在网站开发中,标签<iframe>存在一些跨域问题,需要得到解决。 解决这些问题的一种方案是使用反向代理服务器。Nginx是一个功能强大的开源服务器软件,可以用来提供反向代理服务,也可以作为Web服务器、邮件服务器和负载均衡器。 在使用Nginx作为反向代理服务器时,可以按如下步骤解决iframe跨域问题: 1.安装和配置Nginx。首先,您需要在服务器上安装Nginx,并确保nginx.conf文件正确配置。配置反向代理服务器,将请求从原始服务器发送到新的服务器。 2.设置虚拟主机。为了使Nginx适用于您的网站,需要设置虚拟主机,配置主机的ip格式和端口号。通常情况下,虚拟主机可以支持多个域名和主机名,可以同时接收多个请求。 3.设置location指令。为了完成反向代理任务,可以使用location指令,将请求传递给正确的服务器,并且从指定的URL获取响应。具体而言,您需要在nginx.conf文件中指定location指令,并告诉Nginx需要向哪个服务器发送请求。 4.启用SSL。如果您的网站需要安全的传输,比如HTTPS,那么您可以使用SSL/TLS加密功能。在Nginx中配置SSL,需要使用SSL module或者OpenSSL来启用。 总之,Nginx是一个非常强大的反向代理服务器,可以很好地解决网站开发中的问题。利用其反向代理功能,您可以很容易地解决iframe跨域访问的问题,确保您的Web应用程序能够正常运行并保持安全性和高可用性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值