http跨域解决方案 (nginx配置、含header、cookie跨域、复杂请求,OPTIONS处理)

参考:

https://www.cnblogs.com/hnsongbiao/p/14011468.html

 https://zhuanlan.zhihu.com/p/430733327

 https://www.cnblogs.com/fighter007/p/10917026.html

 

知识预备:

1.跨域的问题不是服务器的问题,是浏览器不允许跨域 从而报错。

2.协议  ip  端口,只要其中一个前后端不同,浏览器都视为跨域。

3.只有X-Requested-With为XMLHttpRequest的情况下才会发生跨域的问题。 而$.get  $.post  $.ajax都是XMLHttpRequest的类型,所以产生跨域问题。getJsonP就是通过绕过这个类型来解决跨域的问题的。

4.Cookie跨域:不能由前端跨域设置,例如如果页面时127.0.0.1,无法再127.0.0.1的页面向localhost植入Cookie。只能在localhost本域注入Cookie,127.0.0.1可以跨域传输localhost的Cookie给后端(后端也是localhost域)。因此跨域Cookie的植入一般由后端完成。

解决方案

5.为了安全,建议由后端植入Cookie,并且设置为HttpOnly。以防止脚本操纵核心Cookie。事实上现在浏览器比较严格,一般建议设置为:

 httponly; secure; SameSite=None
具体参考下面的nginx配置

6.非简单请求,会发送OPTIONS预检命令,看是否支持某些字段的修改,例如自定义Header,自定义Content-Type(浏览器默认的类型只有如下4种)等。发送预检命令的时间由Access-Control-Max-Age来定,单位为秒。

          Content-type默认的4种,如果是其他类型,就会触发非简单请求,也就是会发送预检命令。

          application/x-www-form-urlencoded;charset=utf-8

          multipart/form-data;

          application/json;charset=utf-8

          text/xml

 

0:复杂请求的流程

     复杂请求会先发送一个Options指令,这个时候后端必须返回如下的信息:

HTTP/1.1 204 No Content
Server: nginx/1.22.1
Date: Wed, 09 Aug 2023 07:26:51 GMT
Connection: keep-alive
Access-Control-Allow-Origin: http://127.0.0.1 #跨域时必须
Access-Control-Allow-Credentials: true           #跨域传输Cookie时必须
Access-Control-Allow-Methods: POST              #复杂请求时候的请求方法
Access-Control-Allow-Headers: h1,h2             #复杂请求时候的自定义头
Access-Control-Max-Age: 5                           #预检过期时间,秒

  这里需要注意:复杂请求只有在跨域的情况下才会触发option指令。也就是如果在同域的情况下,即便是复杂请求,也不会触发option。

 

跨域具体方案:

1. 让后台修改,支持某些前端域名的跨域。php代码如下:

     header('Access-Control-Allow-Origin:*'); //支持所有前端域名,但是*有个问题,就是不支持Cookie的跨域

     也可以动态设置跨域。header('Access-Control-Allow-Origin:'.$_SERVER['HTTP_ORIGIN']);//如果请求涉及到跨域,那么origin会自动带上前端域名信息。这样做还有一个好处,可以支持cookie跨域

2. jsonp。 讲get请求伪装成一个script文件的加载。就可以绕过跨域的问题为了。

                  缺点:需要后台做修改;只能用get方法;发出去的不是xhr请求

   jquery的jsonp底层实现原理如下:    

<body>
<script>
	function showData (result) {
		alert(result.message);
	}
	$(document).ready(function () {
		$("#btn").click(function () {
			$("head").append("<script src='http://localhost/frontpage/test.php?callback=showData'><\/script>");
		});
	});
</script>
	<input type='text' id='text' />
	<button id="btn">btn</button>
</body>
</html>

  

<?php
	header('Content-Type:application/json; charset=utf-8');
	$output = array(
					"status"=>-100,
					"message"=>"返回资讯",
	        		"data"=>NULL
				);
	echo 'showData'.'('.json_encode($output).')';	//post request
	exit(0);

 jsonp的使用如下:

<script>
	//function showData (result) {
	//	alert(result.message+"ddg");
	//}
	$(document).ready(function () {
		$("#btn").click(function () {
			$.ajax({
				url: "http://localhost/frontpage/test.php",
				type: "GET",
				dataType: "jsonp",  //指定服务器返回的数据类型
				//jsonpCallback: "showData",  //指定回调函数名称
				success: function (result) {
					alert(result.message);
				}
			});
		});
	});
</script>
	<input type='text' id='text' />
	<button id="btn">btn</button>
</body>
</html>

  

<?php
	header('Content-Type:application/json; charset=utf-8');
	$output = array(
					"status"=>-100,
					"message"=>"返回资讯",
	        		"data"=>NULL
				);
	echo $_GET['callback'].'('.json_encode($output).')';	//post request
	exit(0);

  

3. 自定义header进行跨域,跨域设置Cookie【关键内容、关键内容、关键内容】

    自定义的Header跨域可以通过:前端设置Header、后端设置Access-Control-Allow-Headers来解决。

    Cookie跨域,只能通过后端设置(前端配合);不能通过前端直接设置跨域的Cookie。

    简单请求不会触发OPTIONS命令,非简单请求会触发OPTIONS,可以通过MAX-AGE设置触发OPTIONS的间隔。

    简单请求和非简单请求可以参看百度。

 前端代码示例1(ajax):

$.ajax({
	url: 'http://localhost/frontpage/test.php',
	type: 'POST',//or GET DELETE PUT
	cache:false,//务必false,某些浏览器如果不是false,会有缓存。只有HEADER和GET可能有缓存,所以只有他们2个需要手动设置cache为false。其他指令都是默认false,因此强烈建议所有请求都用POST
	
     //注意新增请求头,会首先调用OPTIONS方法,来看后端是否支持。
	headers:{//添加头方法1
		"h1":"yes1",
		"h2":"yes2",
		//"cookie":"my1=3bc"
	},
	beforeSend:function(xhr){//添加头方法二,可以设置header,cookie
    		xhr.setRequestHeader("h3",34444);
		xhr.setRequestHeader("h4",34444);
    		//xhr.setRequestHeader("af2","34444");
    		//$.cookie('yy', 4446664444, { expires: 7,path: '/',domain:'other_domain'  });//前端跨域设置,无效果。只能在other_domain本域设置。
   	},
	xhrFields:{//
		withCredentials:true//允许前端把跨域的Cookie带给后端[允许后端向前端植入cookie]。是被调用方域名的cookie
	},
     data: {'title':'test334'},
	success: function(response){
		console.log(response.status)
$.cookie("token",response.data,{expires:7,path:'/'});
location.href='index.html'; }, complete: function(XMLHttpRequest, textStatus,a,b,c,ed,f){ console.log(textStatus); }, error: function(XMLHttpRequest, textStatus, errorThrown){ console.log("dg"); //通常情况下textStatus和errorThrown只有其中一个包含信息 //this; //调用本次ajax请求时传递的options参数 } });

   

前端代码示例1(fetch):

async ajaxGetTransOnlyCalc(){
        let response = await myfetch("http://localhost/data",{
            "data":"jack"
        });
        if(response.status==0){
              XXXX
        }
}

async fetch(url,objData) {
    try {
        let response = await fetch(url,{
            method: 'POST',//这里如果是PUT  DELETE等,会触发复杂请求
            credentials: 'include',//跨域Cookie必须配置
            mode: "cors", //跨域Cookie必须配置
            headers: {
              'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',//如果填的不是常用的4种,会触发复杂请求
              //"AA":"BB"//如果是自定义头,会触发复杂请求
            },
            body: new URLSearchParams(objData).toString(),
        });
        if(response.ok){
          let res = await response.json();
          return res;
        }
        else{
          return {status:-1,message:"ERROR:ajax status is not 2XX [error ajax status is "+response.status+"] "+response.statusText}
        }
    } catch (error) {
        console.log('Request Failed When fetch '+url+objData.toString(), error);
    }

  

nginx的配置

 server {
        listen       80;
        server_name  localhost;
        root   ./html;

        location  /api/ {#所有api的方法都会再次处理,OPTIONS会nginx自行预处理,其他的会交给tomcat【springBoot】处理
            
# 预检请求处理,非简单请求的情况就必须做这样的处理 if ($request_method = 'OPTIONS') { add_header 'Access-Control-Allow-Origin' $http_origin; #允许某一个url跨域请求,不建议设置为* add_header 'Access-Control-Allow-Credentials' 'true'; #跨域植入cookie必须设置为true add_header Access-Control-Allow-Methods $http_access_control_request_method;#允许非简单请求方法,比如DELETE PUT。 add_header Access-Control-Allow-Headers $http_access_control_request_headers;#允许自定义的Header头 add_header Access-Control-Max-Age 5;#过期时间,单位“秒”。可以大一点,比如24*3600*7, return 204; } proxy_pass http://localhost:8081/;#转发给微服务处理 #对于简单请求只需要设置这几个值就行了。 add_header 'Access-Control-Allow-Origin' $http_origin; #允许某一个url跨域请求,不建议设置为* add_header 'Access-Control-Allow-Credentials' 'true'; #跨域植入cookie必须设置为true proxy_cookie_path / "/; httponly; secure; SameSite=None"; #在植入cookie的后方,增加一些说明。这是必须设置的,因为现在浏览器变严格了。这样设置的前提是,必须是Https。切记。 } location /data/ { index index.html index.htm; }

  再次提醒,用以下方法设置跨域的前提必须是https。坑不坑,好麻烦,还是别跨域传输cookie了。

     proxy_cookie_path / "/; httponly; secure; SameSite=None"; 

        http如何实现跨域呢,暂时没研究。

    

以下是php代码,很久不用php也不知道是否能用了,仅做个记录而已,供参考:

<?php
	header('Content-Type:application/json; charset=utf-8');//设置返回格式
	//header('Access-Control-Allow-Headers:h1,h2,h3,h4');//支持自定义的header 
	header('Access-Control-Allow-Origin:'.$_SERVER['HTTP_ORIGIN']);//动态支持跨域
	header("Access-Control-Allow-Credentials:true");///支持cookie额跨域,允许后端跨域设置Cookie。
	//setCookie("name","value");
	
	if($_SERVER["REQUEST_METHOD"]=="OPTIONS"){
		header("Access-Control-Max-Age: 5");//设置预检请求的有效期,单位是秒【OPTION】。
		header('Access-Control-Allow-Headers:'.$_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']);//支持自定义的header
		exit(0);
	}
	$output = array(
			"status"=>-100,
			"message"=>"返回资讯",
	             "data"=>$_SERVER
		);
	//echo $_GET['callback'].'('.json_encode($output).')';	//post request
	echo json_encode($output);	//post request
	exit(0);

  

总结一下,跨域传输cookie比较复杂:

(1)需要前端js设置一些东西

(2)需要后端设置一些东西

(3)必须是https协议,然后设置samesite secure之类的东西。

posted @ 2018-07-26 15:36  东方春  阅读(3908)  评论(0编辑  收藏  举报