Ajax跨域、Json跨域、Socket跨域和Canvas跨域等同源策略限制的解决方法

同源是指相同的协议、域名、端口,三者都相同才属于同域。不符合上述定义的请求,则称为跨域。

相信每个开发人员都曾遇到过跨域请求的情况,虽然情况不一样,但问题的本质都可以归为浏览器出于安全考虑下的同源策略的限制。

跨域的情形有很多,最常见的有Ajax跨域、Socket跨域和Canvas跨域。下面列举一些我们常见的跨域情形下,某些浏览器控制台给出的错误提示:

FireFox下的提示:

已阻止交叉源请求:同源策略不允许读取***上的远程资源。可以将资源移动到相同的域名上或者启用 CORS 来解决这个问题。

Canvas跨域Chrome下的提示:

Uncaught SecurityError : Failed to execute ‘getImageData‘ on ‘CanvasRenderingContext2D‘ : The canvas has been tainted by cross-origin data.

或:

Imagefrom origin ‘http://js.xx.com‘ has been blocked from loading by Cross-OriginResource Sharing policy: No ‘Access-Control-Allow-Origin‘ header is present onthe requested resource. Origin ‘http://act.xx.com‘ is therefore not allowedaccess.

技术分享

网上有许多解决跨域的方法,大体上有这几种:

1)document.domain+iframe的设置

2)动态创建script

3)利用iframe和location.hash

4)window.name实现的跨域数据传输

5)使用HTML5 postMessage

6)利用flash

7)通过代理,js访问代理,代理转到不同的域

http://developer.yahoo.com/javascript/howto-proxy.html

8)Jquery JSONP(不能成为真正的Ajax,本质上仍是动态创建script)

http://www.cnblogs.com/chopper/archive/2012/03/24/2403945.html

9)跨域资源共享(CORS) 这是HTML5跨域问题的标准解决方案

说明:方案1~方案6见Rain Man所写的文章《JavaScript跨域总结与解决办法》

http://www.cnblogs.com/rainman/archive/2011/02/20/1959325.html

下面主要就我总结的几种解决跨域的方法,展开说一下。

1)  绕开跨域。

适用情形是:动静分离。

example1.com域名下的页面中跨域请求是以JavaScript内联方式实现的,而请求的目标静态资源是放在example2.com 域名下,这时可以将执行跨域请求的JavaScript代码块独立出来,放到example2.com上,而example1.com页面通过外联方式引 入该静态域名下的js文件。这样,js与请求的图片等资源都在example2.com下,即可正常访问到。这种方法其实是一种巧妙避开跨域的方法。

2)  后台抓取克隆图片。

适用情形:动静不分离(两个域名均运行访问静态资源)。

example1.com请求example2.com下的资源图片,可以使用PHP抓取图片并在example2.com下生成一份,这样就可以间接访问到example1.com的静态资源。

html模板示例代码:

$("#scratchpad").wScratchPad({     //刮刮卡示例,当前域名http://act.xxx.com

width:283,

height:154,

//color: "#a9a9a7",

image2:"imgdata.php?url=http://js.xxx.com/static/activity/sq/guagua315/images/card_inner.jpg",

scratchMove:function() {

}

});

或:

xi= new XMLHttpRequest ();

xi.open( "GET" , "imgdata.php?url=" +yourImageURL, true );

xi.send();

xi.onreadystatechange= function () {

if (xi.readyState== 4 && xi.status== 200 ) {

img= new Image ;

img.onload= function (){

ctx.drawImage(img, 0 , 0 , canvas.width, canvas.height);

}

img.src=xi.responseText;

}

}

PHP处理代码:

<?php //imgdata.php

$url=$_GET[ ‘url‘ ];

$img =file_get_contents($url);

$imgname = substr(strrchr($url, "/" ), 1 );

file_put_contents($fn,$img);

echo $imgname;

?>

上述代码在当前php目录下生成了克隆生成了一张图片。

3)  后台程序设置Access-Control-Allow-Origin

适用情形:Ajax获取跨域接口的JSON数据。

example1.com请求example2.com的数据接口,则需要在example2.com的数据接口添加跨域访问授权。

PHP程序中开始出添加 header (‘HeaderName: Header Value‘); 这样的header标记:

header(‘Access-Control-Allow-Origin:*‘);

4)修改服务器配置启用CORS

适用情形:跨域访问静态资源。

Access-Control-Allow-Origin是什么作用呢?用于授权资源的跨站访问。比如,静态资源图片都放在 example2.com 域名下, 如果在返回的头中没有设置 Access-Control-Allow-Origin , 那么别的域是没有权限外链你的图片的。

要实现CORS跨域,服务端需要这个一个流程,图片引自 html5rocks ,附图如下

技术分享

a.      对于简单请求,如GET,只需要在HTTP Response后添加Access-Control-Allow-Origin。

b.      对于非简单请求,比如POST、PUT、DELETE等,浏览器会分两次应答。第一次preflight(method: OPTIONS),主要验证来源是否合法,并返回允许的Header等。第二次才是真正的HTTP应答。所以服务器必须处理OPTIONS应答。

这里是一个nginx启用CORS的参考配置示例 http://enable-cors.org/server_nginx.html 。代码:

#
# A CORS (Cross-Origin Resouce Sharing) config for nginx
#
# == Purpose
#
# This nginx configuration enables CORS requests in the following way:
# - enables CORS just for origins on a whitelist specified by a regular expression
# - CORS preflight request (OPTIONS) are responded immediately
# - Access-Control-Allow-Credentials=true for GET and POST requests
# - Access-Control-Max-Age=20days, to minimize repetitive OPTIONS requests
# - various superluous settings to accommodate nonconformant browsers
#
# == Comment on echoing Access-Control-Allow-Origin
# 
# How do you allow CORS requests only from certain domains? The last
# published W3C candidate recommendation states that the
# Access-Control-Allow-Origin header can include a list of origins.
# (See: http://www.w3.org/TR/2013/CR-cors-20130129/#access-control-allow-origin-response-header )
# However, browsers do not support this well and it likely will be
# dropped from the spec (see, http://www.rfc-editor.org/errata_search.php?rfc=6454&eid=3249 ).
# 
# The usual workaround is for the server to keep a whitelist of
# acceptable origins (as a regular expression), match the request‘s
# Origin header against the list, and echo back the matched value.
#
# (Yes you can use ‘*‘ to accept all origins but this is too open and
# prevents using ‘Access-Control-Allow-Credentials: true‘, which is
# needed for HTTP Basic Access authentication.)
#
# == Comment on  spec
#
# Comments below are all based on my reading of the CORS spec as of
# 2013-Jan-29 ( http://www.w3.org/TR/2013/CR-cors-20130129/ ), the
# XMLHttpRequest spec (
# http://www.w3.org/TR/2012/WD-XMLHttpRequest-20121206/ ), and
# experimentation with latest versions of Firefox, Chrome, Safari at
# that point in time.
#
# == Changelog
#
# shared at: https://gist.github.com/algal/5480916
# based on: https://gist.github.com/alexjs/4165271
#
location / {
    # if the request included an Origin: header with an origin on the whitelist,
    # then it is some kind of CORS request.
    # specifically, this example allow CORS requests from
    #  scheme    : http or https
    #  authority : any authority ending in ".mckinsey.com"
    #  port      : nothing, or :
    if ($http_origin ~* (https?://[^/]*\.mckinsey\.com(:[0-9]+)?)$) {
  set $cors "true";
    }
    # Nginx doesn‘t support nested If statements, so we use string
    # concatenation to create a flag for compound conditions
    # OPTIONS indicates a CORS pre-flight request
    if ($request_method = ‘OPTIONS‘) {
  set $cors "${cors}options";  
    }
    # non-OPTIONS indicates a normal CORS request
    if ($request_method = ‘GET‘) {
  set $cors "${cors}get";  
    }
    if ($request_method = ‘POST‘) {
  set $cors "${cors}post";
    }
    # if it‘s a GET or POST, set the standard CORS responses header
    if ($cors = "trueget") {
  # Tells the browser this origin may make cross-origin requests
  # (Here, we echo the requesting origin, which matched the whitelist.)
  add_header ‘Access-Control-Allow-Origin‘ "$http_origin";
  # Tells the browser it may show the response, when XmlHttpRequest.withCredentials=true.
  add_header ‘Access-Control-Allow-Credentials‘ ‘true‘;
  # # Tell the browser which response headers the JS can see, besides the "simple response headers"
  # add_header ‘Access-Control-Expose-Headers‘ ‘myresponseheader‘;
    }
    if ($cors = "truepost") {
  # Tells the browser this origin may make cross-origin requests
  # (Here, we echo the requesting origin, which matched the whitelist.)
  add_header ‘Access-Control-Allow-Origin‘ "$http_origin";
  # Tells the browser it may show the response, when XmlHttpRequest.withCredentials=true.
  add_header ‘Access-Control-Allow-Credentials‘ ‘true‘;
  # # Tell the browser which response headers the JS can see, besides the "simple response headers"
  # add_header ‘Access-Control-Expose-Headers‘ ‘myresponseheader‘;
    }
    # if it‘s OPTIONS, then it‘s a CORS preflight request so respond immediately with no response body
    if ($cors = "trueoptions") {
  # Tells the browser this origin may make cross-origin requests
  # (Here, we echo the requesting origin, which matched the whitelist.)
  add_header ‘Access-Control-Allow-Origin‘ "$http_origin";
  # in a preflight response, tells browser the subsequent actual request can include user credentials (e.g., cookies)
  add_header ‘Access-Control-Allow-Credentials‘ ‘true‘;
  #
  # Return special preflight info
  #
  # Tell browser to cache this pre-flight info for 20 days
  add_header ‘Access-Control-Max-Age‘ 1728000;
  # Tell browser we respond to GET,POST,OPTIONS in normal CORS requests.
  #
  # Not officially needed but still included to help non-conforming browsers.
  #
  # OPTIONS should not be needed here, since the field is used
  # to indicate methods allowed for "actual request" not the
  # preflight request.
  #
  # GET,POST also should not be needed, since the "simple
  # methods" GET,POST,HEAD are included by default.
  #
  # We should only need this header for non-simple requests
  # methods (e.g., DELETE), or custom request methods (e.g., XMODIFY)
  add_header ‘Access-Control-Allow-Methods‘ ‘GET, POST, OPTIONS‘;
  # Tell browser we accept these headers in the actual request
  #
  # A dynamic, wide-open config would just echo back all the headers
  # listed in the preflight request‘s
  # Access-Control-Request-Headers.
  #
  # A dynamic, restrictive config, would just echo back the
  # subset of Access-Control-Request-Headers headers which are
  # allowed for this resource.
  #
  # This static, fairly open config just returns a hardcoded set of
  # headers that covers many cases, including some headers that
  # are officially unnecessary but actually needed to support
  # non-conforming browsers
  # 
  # Comment on some particular headers below:
  #
  # Authorization -- practically and officially needed to support
  # requests using HTTP Basic Access authentication. Browser JS
  # can use HTTP BA authentication with an XmlHttpRequest object
  # req by calling
  # 
  #   req.withCredentials=true,  and
  #   req.setRequestHeader(‘Authorization‘,‘Basic ‘ + window.btoa(theusername + ‘:‘ + thepassword))
  #
  # Counterintuitively, the username and password fields on
  # XmlHttpRequest#open cannot be used to set the authorization
  # field automatically for CORS requests.
  #
  # Content-Type -- this is a "simple header" only when it‘s
  # value is either application/x-www-form-urlencoded,
  # multipart/form-data, or text/plain; and in that case it does
  # not officially need to be included. But, if your browser
  # code sets the content type as application/json, for example,
  # then that makes the header non-simple, and then your server
  # must declare that it allows the Content-Type header.
  # 
  # Accept,Accept-Language,Content-Language -- these are the
  # "simple headers" and they are officially never
  # required. Practically, possibly required.
  #
  # Origin -- logically, should not need to be explicitly
  # required, since it‘s implicitly required by all of
  # CORS. officially, it is unclear if it is required or
  # forbidden! practically, probably required by existing
  # browsers (Gecko does not request it but WebKit does, so
  # WebKit might choke if it‘s not returned back).
  #
  # User-Agent,DNT -- officially, should not be required, as
  # they cannot be set as "author request headers". practically,
  # may be required.
  # 
  # My Comment:
  #
  # The specs are contradictory, or else just confusing to me,
  # in how they describe certain headers as required by CORS but
  # forbidden by XmlHttpRequest. The CORS spec says the browser
  # is supposed to set Access-Control-Request-Headers to include
  # only "author request headers" (section 7.1.5). And then the
  # server is supposed to use Access-Control-Allow-Headers to
  # echo back the subset of those which is allowed, telling the
  # browser that it should not continue and perform the actual
  # request if it includes additional headers (section 7.1.5,
  # step 8). So this implies the browser client code must take
  # care to include all necessary headers as author request
  # headers.
  # 
  # However, the spec for XmlHttpRequest#setRequestHeader
  # (section 4.6.2) provides a long list of headers which the
  # the browser client code is forbidden to set, including for
  # instance Origin, DNT (do not track), User-Agent, etc.. This
  # is understandable: these are all headers that we want the
  # browser itself to control, so that malicious browser client
  # code cannot spoof them and for instance pretend to be from a
  # different origin, etc..
  #
  # But if XmlHttpRequest forbids the browser client code from
  # setting these (as per the XmlHttpRequest spec), then they
  # are not author request headers. And if they are not author
  # request headers, then the browser should not include them in
  # the preflight request‘s Access-Control-Request-Headers. And
  # if they are not included in Access-Control-Request-Headers,
  # then they should not be echoed by
  # Access-Control-Allow-Headers. And if they are not echoed by
  # Access-Control-Allow-Headers, then the browser should not
  # continue and execute actual request. So this seems to imply
  # that the CORS and XmlHttpRequest specs forbid certain
  # widely-used fields in CORS requests, including the Origin
  # field, which they also require for CORS requests.
  #
  # The bottom line: it seems there are headers needed for the
  # web and CORS to work, which at the moment you should
  # hard-code into Access-Control-Allow-Headers, although
  # official specs imply this should not be necessary.
  # 
  add_header ‘Access-Control-Allow-Headers‘ ‘Authorization,Content-Type,Accept,Origin,User-Agent,DNT,Cache-Control,X-Mx-ReqToken,Keep-Alive,X-Requested-With,If-Modified-Since‘;
  # build entire response to the preflight request
  # no body in this response
  add_header ‘Content-Length‘ 0;
  # (should not be necessary, but included for non-conforming browsers)
  add_header ‘Content-Type‘ ‘text/plain charset=UTF-8‘;
  # indicate successful return with no content
  return 204;
    }
    # --PUT YOUR REGULAR NGINX CODE HERE--
}

服务器解析流程如下:

a.首先查看http头部有无origin字段;

b.如果没有,或者不允许,直接当成普通请求处理,结束;

c.如果有并且是允许的,那么再看是否是preflight(method=OPTIONS);

d.如果是preflight,就返回Allow-Headers、Allow-Methods等,内容为空;

e.如果不是preflight,就返回Allow-Origin、Allow-Credentials等,并返回正常内容。

若服务器为nginx,可以 在 nginx 的 conf 文件中加入以下内容:

location / {
  add_header Access-Control-Allow-Origin *;
}

若服务器为Apache,则可以按照如下配置:

<IfModule mod_setenvif.c>
    <IfModule mod_headers.c>
        <FilesMatch "\.(cur|gif|ico|jpe?g|png|svgz?|webp)$">
            SetEnvIf Origin ":" IS_CORS
            Header set Access-Control-Allow-Origin "*" env=IS_CORS
        </FilesMatch>
    </IfModule>
</IfModule>

为安全起见,Access-Control-Allow-Origin也可设为特定域名的方式。

在HTML5中,有些HTML元素为CORS提供了支持,如img、video新增了 crossOrigin 属性,属性值可以为 anonymous 或 use-credentials 。比如, canvas 绘图要用到跨域图片,在 JavaScript 中要设置 img . crossOrigin = "Anonymous" ;

var img = new Image,
  canvas = document.createElement("canvas"),
  ctx = canvas.getContext("2d"),
  src = "http://example.com/image"; // insert image url here
img.crossOrigin = "Anonymous";
img.onload = function() {
  canvas.width = img.width;
  canvas.height = img.height;
  ctx.drawImage( img, 0, 0 );
  localStorage.setItem( "savedImageData", canvas.toDataURL("image/png") );
}
img.src = src;
// make sure the load event fires for cached images too
if ( img.complete || img.complete === undefined ) {
  img.src = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==";
  img.src = src;
}

上述配置完成后,重启服务器,CORS启用。

然后我们再刷新页面,查询请求头的参数,可以发现多出一个:Access-Control-Allow-Origin:*

,到此证明服务器配置已经生效。同时我们的canvas绘图也可以正常使用了。

刷新页面返回请求响应结果后,HTTP Request Headers的内容:

Remote Address:222.132.18.xx:80

Request URL:http://js.xx.com/static/activity/sq/guagua315/images/card_inner.jpg

Request Method:GET

Status Code:200 OK

Request Headersview source

Accept:image/webp,*/*;q=0.8

Accept-Encoding:gzip, deflate, sdch

Accept-Language:zh-CN,zh;q=0.8

Cache-Control:no-cache

Connection:keep-alive

Host:js.xx.com

Origin:http://act.xx.com

Pragma:no-cache

RA-Sid:7CCAD53E-20140704-054839-03c57a-85faf2

RA-Ver:2.8.8

Referer:http://act.xx.com/sq/guagua315?uuid=46124642&fid=2&sign=xxx

User-Agent:Mozilla/5.0 (Windows NT 6.1;WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.115Safari/537.36

Response Headersview source

Accept-Ranges:bytes

Access-Control-Allow-Origin:*

Connection:close

Content-Length:4010

Content-Type:image/jpeg

Date:Thu, 12 Mar 2015 02:29:43 GMT

ETag:"54f7d1b4-faa"

Last-Modified:Thu, 05 Mar 2015 03:47:00 GMT

Powered-By-ChinaCache:MISS fromCNC-WF-3-3X6

Powered-By-ChinaCache:MISS fromCNC-WF-3-3X5

Server:Tengine

Switch:FSCS

附图:

技术分享

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。