同源策略

浏览器的同源策略
https://developer.mozilla.org/zh-CN/docs/Web/Security/Same-origin_policy

所谓同源是指”协议+域名+端口”三者相同
IE 的同源检测不包括端口

页面可以用document.domain改写自己的源,
调用document.domain会将域的端口值置为null.
只能改为父级域名, 比如store.a.com:81改为a.com

同源限制只是浏览器的安全策略,不是http协议的内容
对于向不同域下发起的请求,浏览器会照常发出,服务端处理后也会正常返回结果,但结果会在返回后被浏览器拦截

同源策略防止了哪些风险
Ajax同源 防CSRF
DOM同源 防页面隐私信息泄漏

跨域访问

Cross-Origin Resource Sharing (CORS)
https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS

跨域资源共享CORS详解 @阮一峰
http://www.ruanyifeng.com/blog/2016/04/cors.html

不要再问我跨域的问题了
https://segmentfault.com/a/1190000015597029

简单请求

请求方法是以下三种方法之一

  • HEAD
  • GET
  • POST

HTTP的头信息不超出以下几种字段

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

Apart from the headers automatically set by the user agent (for example, Connection, User-Agent, or the other headers defined in the Fetch spec as a “forbidden header name”), the only headers which are allowed to be manually set are those which the Fetch spec defines as a “CORS-safelisted request-header”

其他要求

No event listeners are registered on any XMLHttpRequestUpload object used in the request; these are accessed using the XMLHttpRequest.upload property.

No ReadableStream object is used in the request.

对于简单请求,浏览器直接发出CORS请求。
具体来说,就是在头信息之中,增加一个Origin字段。
若被允许, 返回的头信息会包含Access-Control-Allow-Origin字段

  1. Access-Control-Allow-Origin: http://api.bob.com

不被允许的返回, 状态码也可能200

其他相关Header

  1. Access-Control-Allow-Origin: http://api.bob.com
  2. Access-Control-Allow-Credentials: true
  3. Access-Control-Expose-Headers: FooBar

image.png

非简单请求

非简单请求是那种对服务器有特殊要求的请求,比如请求方法是PUTDELETE,或者Content-Type字段的类型是application/json

先发预检

“预检”请求用的请求方法是OPTIONS,表示这个请求是用来询问的。头信息里面,关键字段是Origin,表示请求来自哪个源。

除了Origin字段,”预检”请求的头信息包括两个特殊字段。

Access-Control-Request-Method

该字段是必须的,用来列出浏览器的CORS请求会用到哪些HTTP方法,上例是PUT

Access-Control-Request-Headers

该字段是一个逗号分隔的字符串,指定浏览器CORS请求会额外发送的头信息字段,上例是X-Custom-Header

支持跨域的方式

服务端不支持跨域的话会提示:

No ‘Access-Control-Allow-Origin’ header is present on the requested resource. Origin ‘http://xxx.com‘ is therefore not allowed access.

服务端可以修改的情况

对允许的请求, 响应请求时加上以下头:

  1. response.setHeader("Access-Control-Allow-Origin", "*");

允许所有跨域请求的简单实现:

  1. //allow custom header and CORS
  2. app.all('*', function (req, res, next) {
  3. res.header('Access-Control-Allow-Origin', '*');
  4. res.header('Access-Control-Allow-Headers', 'Content-Type, Content-Length, Authorization, Accept, X-Requested-With , yourHeaderFeild');
  5. res.header('Access-Control-Allow-Methods', 'PUT, POST, GET, DELETE, OPTIONS');
  6. if (req.method == 'OPTIONS') {
  7. res.send(200); // 让options请求快速返回
  8. }
  9. else {
  10. next();
  11. }
  12. });

express

有封装好的中间件, 支持多种配置:

https://github.com/expressjs/cors

koa / thinkjs

用 @koa/cors 或者 kcors

  1. const cors = require('@koa/cors');
  2. module.exports = [
  3. {
  4. handle: 'meta',
  5. options: {
  6. logRequest: isDev,
  7. sendResponseTime: isDev
  8. }
  9. },
  10. {
  11. handle: cors
  12. },
  13. // ...
  14. ]

ThinkJS 中如何解决跨域 https://zhuanlan.zhihu.com/p/37506089

服务端无法修改的情况

如果不是自己开发的,那么可以自己写个后端转发该请求,用代理的方式实现。

或者配置Nginx反向代理

  1. location /api/ {
  2. rewrite ^/api/(.*)$ /$1 break; #所有对后端的请求加一个api前缀方便区分,真正访问的时候移除这个前缀
  3. # API Server
  4. proxy_pass http://serverB.com; #将真正的请求代理到serverB,即真实的服务器地址,ajax的url为/api/user/1的请求将会访问http://www.serverB.com/user/1
  5. }

jsonp跨域

jsonp是一种非正式传输协议
现较少使用了

原理:
凡是拥有”src”这个属性的标签都拥有跨域的能力,比如<script> <img> <iframe>
jsonp实际就是动态添加script标签, 需要的函数名通过callback参数传下去给服务端, 服务端返回数据时会将这个callback参数作为函数名来包裹住JSON数据

  1. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
  2. <html xmlns="http://www.w3.org/1999/xhtml">
  3. <head>
  4. <title></title>
  5. <script type="text/javascript">
  6. var localHandler = function(data){
  7. alert('我是本地函数,可以被跨域的remote.js文件调用,远程js带来的数据是:' + data.result);
  8. };
  9. </script>
  10. <script type="text/javascript" src="http://remoteserver.com/remote.js"></script>
  11. </head>
  12. <body>
  13. </body>
  14. </html>
  1. // remote.js
  2. localHandler({ "result": "我是远程js带来的数据" });

https://www.cnblogs.com/dowinning/archive/2012/04/19/json-jsonp-jquery.html

jquery中使用jsonp:

  1. <script src="https://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script>
  2. <script>
  3. var req = "https://en.wikipedia.org/w/api.php?format=json&action=query&generator=searc…=max&exintro&explaintext&exsentences=1&exlimit=max&gsrsearch=ddd";
  4. // 可以获取得到
  5. $.getJSON(req + "&callback=?", function (data) {
  6. console.log(data);
  7. });
  8. //错误: No 'Access-Control-Allow-Origin' header is present on the requested resource.
  9. $.get(req, function (data) {
  10. console.log(data);
  11. })
  12. </script>

变通实现

使用localstorage代替cookie实现跨域共享数据
https://zhuanlan.zhihu.com/p/35738376
iframe + localstorage + postMessage, 实现了用同一根域下的不同域之间共享数据
Safari不支持 (?)

cross-storage
https://github.com/zendesk/cross-storage
类似上面一篇的一个库

canvas跨域问题

对于跨域的图片,只要能够在网页中正常显示出来,就可以使用canvas的drawImage() API绘制出来。

但是如果你想更进一步,通过getImageData()方法获取图片的完整的像素信息,则多半会出错。

在HTML5中,有些元素提供了支持CORS(Cross-Origin Resource Sharing)(跨域资源共享)的属性,这些元素包括<img><video><script>等,而提供的属性名就是crossOrigin属性。

crossOrigin可以有下面两个值
anonymous 元素的跨域资源请求不需要凭证标志设置
use-credentials 元素的跨域资源请求需要凭证标志设置,意味着该请求需要提供凭证
只要crossOrigin的属性值不是use-credentials,全部都会解析为anonymous

crossOrigin=anonymous相当于告诉对方服务器,你不需要带任何非匿名信息过来。例如cookie,因此,当前浏览器肯定是安全的
“anonymous” keyword means that there will be no exchange of user credentials via cookies

  1. var canvas = document.createElement('canvas');
  2. var context = canvas.getContext('2d');
  3. var img = new Image();
  4. img.crossOrigin = '';
  5. img.onload = function () {
  6. context.drawImage(this, 0, 0);
  7. context.getImageData(0, 0, this.width, this.height);
  8. };
  9. img.src = 'https://avatars3.githubusercontent.com/u/496048?s=120&v=4';

参考链接

图片跨域 https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_enabled_image

crossOrigin属性 https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes

大致是以上两条的综合 https://www.zhangxinxu.com/wordpress/2018/02/crossorigin-canvas-getimagedata-cors/

iframe跨域问题

You can’t access an <iframe> with different origin using JavaScript, it would be a huge security flaw if you could do it.

For the same-origin policy browsers block scripts trying to access a frame with a different origin.

https://stackoverflow.com/questions/25098021/securityerror-blocked-a-frame-with-origin-from-accessing-a-cross-origin-frame

会报如下错误

Blocked a frame with origin from accessing a cross-origin frame

解决: 使用postMessage来传递消息

In your main page:

  1. var frame = document.getElementById('your-frame-id');
  2. frame.contentWindow.postMessage(/*any variable or object here*/, '*');

In your <iframe> (contained in the main page):

  1. window.addEventListener('message', function(event) {
  2. // IMPORTANT: Check the origin of the data!
  3. if (~event.origin.indexOf('http://yoursite.com')) {
  4. // The data has been sent from your site
  5. // The data sent with postMessage is stored in event.data
  6. console.log(event.data);
  7. } else {
  8. // The data hasn't been sent from your site!
  9. // Be careful! Do not use it.
  10. return;
  11. }
  12. });

https://stackoverflow.com/questions/3102819/disable-same-origin-policy-in-chrome

  1. chromium-browser --disable-web-security --user-data-dir="[some directory here]"

script标签

做异常监控的时候 需要给跨域的 script 标签加上 crossorigin, 否则错误信息不全

crossorigin
Normal script elements pass minimal information to the window.onerror for scripts which do not pass the standard CORS checks. To allow error logging for sites which use a separate domain for static media, use this attribute. See CORS settings attributes for a more descriptive explanation of its valid arguments.
https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script

其他

axios

  1. // withCredentials表示跨域请求时是否需要使用凭证
  2. withCredentials: false, // 默认false

withCredentials的情况下,后端要设置Access-Control-Allow-Origin为你的源地址,例如http://localhost:8080,不能是 *

而且还要设置header('Access-Control-Allow-Credentials: true');
https://segmentfault.com/q/1010000015791317
https://segmentfault.com/q/1010000007665348

关于withCredentials:

The XMLHttpRequest.withCredentials property is a Boolean that indicates whether or not cross-site Access-Control requests should be made using credentials.

such as cookies, authorization headers or TLS client certificates.

Setting withCredentials has no effect on same-site requests.

XMLHttpRequest.withCredentials also used to indicate when cookies are to be ignored in the response.

if false (default), XMLHttpRequest from a different domain cannot set cookie.

if true, cookie can be set, but also cannot be accessed by the requesting script through document.cookie or from response headers

https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/withCredentials

fetch

  1. fetch('https://example.com', {
  2. credentials: 'include'
  3. })

credentials原本默认为”omit”, 后来改为” same-origin”了

fetch 不会从服务端发送或接收任何cookies, 如果站点依赖于用户 session,则会导致未经认证的请求(要发送 cookies,必须设置 credentials 选项)。
自从2017年8月25日后,默认的credentials政策变更为same-origin, Firefox也在61.0b13中改变默认值

https://developer.mozilla.org/zh-CN/docs/Web/API/Fetch_API/Using_Fetch
https://developer.mozilla.org/zh-CN/docs/Web/API/WindowOrWorkerGlobalScope/fetch