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
解决方法添加在 参数 后面 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
4、建议不使用cookie 跨域,可以传递指定参数
相关文章:
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 框架,当前文件为中间件。
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>