Ajax 跨域携带 cookie、header 信息、上传文件

原生 js 中 XMLHttpRequest

jquery 中 ajax

axios 请求

ajax 中出现的问题

文件上传出现的问题:

1、上传完成后服务器要过会才可以看到上传文件
2、上传失败,还 显示进度条和 “文件上传完成”

原因:XMLHttpRequestUpload progress 事件大约每 50 毫秒分派(dispatch)一次。 XMLHttpRequestUpload 调度事件关于 body 传输的字节。据了解, XMLHttpRequestUpload 不监控与 body 传输字节相关的网络性能,也不等待服务器的响应。不是真实的上传到服务器的进度。

设置请求头Cookie 后,提示: Refused to set unsafe header "Cookie" ,原因是w3c中不允许手动设置cookie,但可以在 请求标头 中携带已设置的cookie。w3c规定,当请求的 header 匹配以下 不安全字符 时,将被终止:

Accept-Charset
Accept-Encoding
Connection
Content-Length
Cookie
Cookie2
Content-Transfer-Encoding
Date
Expect
Host
Keep-Alive
Referer
TE
Trailer
Transfer-Encoding
Upgrade
User-Agent
Via
View Code

解决方法添加在 参数 后面 canshu+='&'+document.cookie; ,或自定义 header 中,例如  xhr.setRequestHeader("web-cookie","cookie_value"); 

如果 header 设置放 open 前面提示(原生 js 中 XMLHttpRequest  与 jquery 中的 ajax):

Uncaught DOMException: Failed to execute 'setRequestHeader' on 'XMLHttpRequest': The object's state must be OPENED.

跨域携带 cookie 问题:withCredentials

支持withCredentials属性的浏览器有Firefox 3.5+、Safari 4+和Chrome。IE10及更早版本都不支持。在jQuery1.5中,withCredentials这个属性不在原生的xhr中,所以这个请求会被忽略。

如果在同域下配置 xhr.withCredentials 是无效的,同域下 请求标头 默认携带 Cookie 。跨域时当配置了xhr.withCredentials = true时,必须在后端指定域名,例如: header('Access-Control-Allow-Origin:http://aa.com'); ,而不能指定为*。服务器接收带凭据的请求,需要会用 header('Access-Control-Allow-Credentials:true'); 响应。Credentials必须在前后端都被配置,才能使带credentials的CORS请求成功。

如果发送的是带凭据的请求,但服务器的相应中没有包含这个头部,那么浏览器就不会把相应交给JavaScript(于是,responseText中将是空字符串,status的值为0,而且会调用onerror()事件处理程序)。另外,服务器还可以在Preflight响应中发送这个HTTP头部,表示允许源发送带凭据的请求。

前端获取服务端响应的cookie 

1、服务设置向前端公开的header  header('Access-Control-Expose-Headers:Set-Cookie') 

2、cookie 跨域需要设置 

//  cookie 启用安全传输
'secure'    => true,
// httponly设置
'httponly'  => false,
// 是否使用 cookie
  'setcookie' => true,
 //  None || Lax || Strict
 'samesite' => 'None'

3、获取服务端数据后,前端获取cookie

1 // js/jquery  取得响应的 cookie + 浏览器本身的cookie,
2 console.log(document.cookie);
3 // js 中 数据请求成功,获取响应头信息
4 console.log(xhr.getAllResponseHeaders())
5 // jquery 中 ajax 也可使用
6 console.log("Cookie: " + response.getResponseHeader("Set-Cookie"));
7  // jquery 中数据获取成功,获取响应头信息
8  console.log(response.getAllResponseHeaders())

第 4 行 与第 8 行 获取 响应头信息,以及 axios 响应拦截器中的 console.log(response.headers);   在同域、跨域下获取的 header 信息相同。

以下是同域下获取的信息,但是没有获取到 set-cookie,原因不知?

access-control-allow-credentials: true
access-control-allow-headers: X-Requested-With,Content-Type,XX-Device-Type,XX-Token,XX-Api-Version,XX-Wxapp-AppId,Authorization,Cookie
access-control-allow-methods: GET,POST,PATCH,PUT,DELETE,OPTIONS
access-control-allow-origin: http://gohosts.com
access-control-expose-headers: Set-Cookie
connection: Keep-Alive
content-type: application/json; charset=utf-8
date: Sat, 04 Feb 2023 06:03:21 GMT
keep-alive: timeout=5, max=99
samesite: None
server: Apache/2.4.23 (Win32) OpenSSL/1.0.2j mod_fcgid/2.3.9
transfer-encoding: chunked
x-powered-by: PHP/7.1.13

在 跨域 下获取的 header 信息 ,我这里使用的是 chrome 浏览器,chrome 没有对 cookie 配置

content-type: application/json; charset=utf-8

同时跨域有客户端兼容问题,导致前端 js 获取不到 set-cookie,未测试:

跨域cookie失效问题 SameSite=None和secure    

Chrome 配置samesite=none方式

Cookie中的SameSite设置

聊聊Cookie的SameSite属性

4、建议不使用cookie 跨域,可以传递指定参数

相关文章:

JavaScript中的AJAX请求 

XMLHttpRequest参数详解

options 预检请求

前端 ajax 有时会发送2次请求,是因为使用了带预检(Preflighted)的跨域请求。先发送一个OPTIONS请求,这个请求叫Preflighted Request(带预检的跨域请求)。预检请求会检测服务器是否支持我们的真实请求所需要的跨域资源,唯有资源满足条件才会发送真实的请求。

如果options获得的回应是拒绝性质的,比如404\403\500等http状态,就会停止post、get等请求的发出。或者前端请求头部增加了authorization项,那么在服务器响应头中需要放入Access-Control-Allow-Headers,Access-Control-Allow-Headers的值中必须要包含authorization,否则OPTIONS预检会失败,从而导致不会发送真实的请求。

以下情况下请求会发送 options 预检请求

// 标号前 * 表示常用
*1. 请求方法不是`GET/HEAD/POST`;
2. HTTP请求头限制这几种字段,`人为设置该集合之外 `的其他首部字段:
    `{Accept、Accept-Language、Content-Language、
    Content-Type(需要注意额外的限制)、
    DPR、Downlink、Save-Data、Viewport-Width、Width}`;
*3. POST请求的`Content-Type`并非其中之一:
      application/x-www-form-urlencoded, 
      multipart/form-data, 
      text/plain
4. 请求中的任意 `XMLHttpRequestUpload` 对象均有注册事件监听器;
*5. 请求设置了自定义的 `header` 字段;
6. 请求中没有使用 `ReadableStream` 对象;

服务端跨域设置

1、必须在 header 中设置 Access-Control-Allow-Origin 的域名,不可设置为 *
2、必须在 header 中设置 Access-Control-Allow-Credentials 为 true

方法一:PHP 文件设置跨域

public function handle($request, \Closure $next)
    {
        // 前端 请求标头 信息设置
        // 跨域问题 允许访问的域名 ,如果携带cookie 不可为 *
        $host_url="http://gohosts.com";
        header('Access-Control-Allow-Origin:'.$host_url);
        // 对请求的响应允许暴露前端的js,服务端使用session 存储,session 是依赖于 cookie,所以前端必须允许携带 cookie
        header('Access-Control-Allow-Credentials:true');
        // 自定义向前端 公开的响应头
        header('Access-Control-Expose-Headers:Set-Cookie');
        header('Access-Control-Allow-Headers:X-Requested-With,Content-Type,XX-Device-Type,XX-Token,XX-Api-Version,XX-Wxapp-AppId,Authorization,Cookie');
        header('Access-Control-Allow-Methods:GET,POST,PATCH,PUT,DELETE,OPTIONS');

        //跨域请求中设置了自定义的header字段, 所以该请求是preflighted request, 则请求前一定会发送一个OPTIONS作为预请求.
        if(strtoupper($request->method())== 'OPTIONS'){
            exit;
        }
        return $next($request);
}    

这个文件使用得 thinkphp 框架,当前文件为中间件。

方法二:nginx 配置跨域

location / {
    index index.php;
    if ($request_method = 'OPTIONS') {
        add_header 'Access-Control-Allow-Origin' 'http://odocker.com';
        // 用来指定本次预检请求的有效期,单位为秒,在此期间不用发出另一条预检请求。 如果值为 -1,则表示禁用缓存
        add_header 'Access-Control-Max-Age' 86400;
        // 对请求的响应允许暴露前端的js
        add_header 'Access-Control-Allow-Credentials' 'true';
        add_header 'Access-Control-Allow-Headers' 'Authorization,Cookie,Content-Type,Accept,Origin,User-Agent,DNT,Cache-Control,X-Mx-ReqToken,X-Requested-With';
        add_header 'Access-Control-Allow-Methods' 'GET,POST,OPTIONS'
        add_header 'Content-Length' 0;
        // 服务器成功处理了请求,但没返回任何内容。
        return 204;
    }
    if (!-e $request_filename) {
        rewrite ^/(.*)$ /index.php?s=$1 last;
    }
}

方法三:apache下修改vhosts中根域名的配置,需要重启 apache

<Directory "/Users/cindy/dev">
AllowOverride ALL
Header set Access-Control-Allow-Origin https://www.google.com,https://www.baidu.com
</Directory>

方法四:修改.htaccess配置文件

SetEnvIf Origin "^http(s)?://(.+\.)?(submit.magazine.ubandev.com|localhost:8080)$" origin_is=$0
Header always set Access-Control-Allow-Origin %{origin_is}e env=origin_is
Header set Access-Control-Allow-Credentials true>

优点:无需修改apache域名配置。静态文件也可设置响应头,可以跨域。正则之后响应头只有一个域名,可以发送cookie。本人未测试。

方法五:修改apache/conf/httpd.conf 文件,需要重启 apache

找到   #LoadModule headers_module modules/mod_headers.so,把#注释符去掉,目的是开启apache头信息自定义模块

缺点:安全性缺失,谁都能访问。相当于完全放弃跨域控制,且无法发送登陆凭证,发送cookie等依然会被拦截。本人未测试。

服务器配置

如果前端使用了  Authorization ,服务端(apache)需要配置, 在根目录创建  .htaccess  文件

// 第一种方法
# Authorization Headers
RewriteCond %{HTTP:Authorization} ^(.+)$
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
// 第二种方法
<IfModule mod_rewrite.c>
    SetEnvIf Authorization .+ HTTP_AUTHORIZATION=$0
</IfModule>

 

posted @ 2022-08-10 14:47  柔和的天空  阅读(2115)  评论(0编辑  收藏  举报