[关闭]
@yangfch3 2016-02-18T15:09:10.000000Z 字数 6796 阅读 3688

跨域

JavaScript


什么是跨域?

只要协议、域名、端口有任何一个不同,都被视为不同的域。

跨域问题的出现是因为浏览器的安全机制导致的,防止 AJAX 形式的跨站恶意脚本攻击,常见出现跨域问题的情景是:


方案零:服务器端脚本构建数据

例如:aspxphpjsp 等服务器端处理程序,都有相应的组件能访问 URL 获取与处理 response,我们可以在服务器端写一个小程序用于获取外域的数据,再对数据进行相应的处理作为返回值,当我们请求这个服务器端程序对应的 URL 时,服务器作为中间人代为请求数据然后返回。

例子:梨山宾馆的天气数据获取方法

浏览器端:AJAX 请求服务器端 aspx 程序

  1. var xhr;
  2. var rootURL = "http://www.lishanguesthouse.com.tw/";
  3. if (window.XMLHttpRequest) {
  4. xhr = new XMLHttpRequest();
  5. } else if (window.ActiveXObject()) {
  6. xhr = new ActiveXObject("Microsoft XMLHTTP");
  7. }
  8. var url=rootURL + "GetWeather.ashx";
  9. xhr.onreadystatechange = success;
  10. xhr.open("POST", url, true);
  11. xhr.send();
  12. function success() {
  13. if (xhr.readyState == 4 && xhr.status == 200) {
  14. var str = xhr.responseText;
  15. str = '(' + str + ')';
  16. var obj = eval(str);
  17. var weatherIcon = document.getElementById("weatherIcon");
  18. var liTemp = document.getElementById("temperature");
  19. var iconNum = obj['img1'];
  20. var tempVal = obj['temp1'];
  21. var weatherIconUrl;
  22. if (iconNum.length==1) {
  23. weatherIconUrl = rootURL+"/Images/weatherIcon/0" + iconNum + ".png";
  24. } else {
  25. weatherIconUrl = rootURL+"/Images/weatherIcon/" + iconNum + ".png";
  26. }
  27. weatherIcon.style.backgroundImage = "url('" + weatherIconUrl + "')";
  28. liTemp.innerHTML = tempVal+" ℃";
  29. }
  30. }

服务器端的 aspx 处理程序:请求 HTML 页面,正则处理,抽取数据,生成 JSON 格式数据,返回

  1. <%@ WebHandler Language="C#" Class="GetWeather" %>
  2. using System;
  3. using System.IO;
  4. using System.Net;
  5. using System.Text;
  6. using System.Text.RegularExpressions;
  7. using System.Web;
  8. public class GetWeather : IHttpHandler {
  9. public void ProcessRequest (HttpContext context) {
  10. //首先获取天气图标的编号,用正则表达式匹配,天气图标数据从中国天气网API获取
  11. string weatherIconUrl = "http://www.weather.com.cn/data/cityinfo/101340401.html";
  12. //根据Url返回对应网页的数据
  13. string weatherIconPage = GetPage(weatherIconUrl);
  14. //获得img1的索引
  15. int weatherIconIndex = weatherIconPage.IndexOf("img1");
  16. //获得img1对应的天气图标的编号
  17. string weatherIconNum = weatherIconPage.Substring(weatherIconIndex + 8, 1);
  18. //以下部分是把网页的table中的气温数据取出来
  19. string tempUrl = "http://www.cwb.gov.tw/V7/observe/24real/Data/C0F86.htm";
  20. string tempPage = GetPage(tempUrl);
  21. int tableStartIndex = tempPage.IndexOf("<table");
  22. int tableEndIndex = tempPage.IndexOf("</table>");
  23. string tempTable = tempPage.Substring(tableStartIndex, tableEndIndex - tableStartIndex + 8);
  24. Regex regExp = new Regex(@"<tr[^>]*>[\s\S]*?<\/tr>");
  25. MatchCollection matches = regExp.Matches(tempTable);
  26. string firstTr = matches[1].Value.ToString();
  27. Regex reg = new Regex(@"<td[^>]*>[\s\S]*?<\/td>");
  28. MatchCollection tdMatches = reg.Matches(firstTr);
  29. string firstTd = tdMatches[0].Value.ToString();
  30. string[] seperators = new string[] { "<", ">" };
  31. string[] components = firstTd.Split(seperators, StringSplitOptions.RemoveEmptyEntries);
  32. string temp1 = components[1];
  33. string weatherJson = @"{'img1':'" + weatherIconNum + "','temp1':'" + temp1 + "'}";
  34. context.Response.Write(weatherJson);
  35. context.Response.End();
  36. }
  37. public string GetPage(string url)
  38. {
  39. HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
  40. HttpWebResponse response = (HttpWebResponse)request.GetResponse();
  41. Stream responseStream = response.GetResponseStream();
  42. string page = string.Empty;
  43. StreamReader streamReader = new StreamReader(responseStream, Encoding.UTF8);
  44. page = streamReader.ReadToEnd();
  45. return page;
  46. }
  47. public bool IsReusable {
  48. get {
  49. return false;
  50. }
  51. }
  52. }

方案一:代理服务器转发

在同源域名下架设一个代理服务器来转发,JavaScript 负责把请求发送到代理服务器:

  1. '/proxy?url=http://www.sina.com.cn'

代理服务器再把结果返回,这样就遵守了浏览器的同源策略。

用脚趾头也能想到这个方法的缺点:你有这个预算搭建代理服务器么?你有这个能力在别人家的域名下架设代理服务器么?


方案二:Flash

Flash 是强大的,虽然安全性与性能问题饱受人诟病。

随着 Flash 的式微,已经极少采用。

跨域:在 Ajax 中使用 Flash 实现跨域数据读取


方案三:JSONP

JSONP 采用在网页中动态插入 script 元素的做法(避开 AJAX 请求,直接请求外部脚本资源),向服务器请求脚本文件。使用 GET 的方式发送请求,并需要在 URL 后面加上参数 ?callback=foo(具体见各个 API 提供方的调用说明),服务器会将 JSON 数据作为一个对象参数放到回调函数 foo 内,返回脚本。

服务器端需要的相应机制:针对特定 GET 请求的 URL,请求数据,将数据作为对象参数传入指定的回调函数,构建包含回调函数的脚本,返回脚本,JSONP 请求完成。

JSONP 作用过程:

在脚本程序内准备好返回数据的处理函数(需要预先知道返回的数据的格式)

foo(data){...}

创建 script 标签,向目标域目标 URL 发起请求,一般需要添加查询参数类似 ?callback=foo

服务器处理请求,返回数据作为参数的回调函数的脚本文件

浏览器加载好 JSONP 请求的脚本,执行预先准备好的回调函数

实例:服务器端

  1. <script type="text/javascript">
  2. function dosomething(jsondata){
  3. // 处理获得的json数据
  4. }
  5. </script>
  6. <script src="http://example.com/data.php?callback=dosomething"></script>

实例:服务器端处理

  1. <?php
  2. $callback = $_GET['callback'];//得到回调函数名
  3. $data = array('a','b','c');//要返回的数据
  4. echo $callback.'('.json_encode($data).')';//输出
  5. ?>

方案四:CORS

CORS 的原理其实很简单,就是自动增加一条 HTTP 头信息的查询,询问服务器端,当前请求的域名是否在许可名单之中,以及可以使用哪些 HTTP 动作。

可见,跨域能否成功,取决于对方服务器是否愿意给你设置一个正确的 Access-Control-Allow-Origin,决定权始终在对方手中。

简单跨域请求 的请求头与响应头信息(GETHEAD 以及 Content-Type 类型
application/x-www-form-urlencodedmultipart/form-datatext/plainpost 请求):

  1. // AJAX Request Header
  2. Origin: http://www.example.com
  3. // AJAX Response Header
  4. Access-Control-Allow-Origin: http://www.example.com
  5. Access-Control-Allow-Methods: POST, GET, OPTIONS
  6. Access-Control-Allow-Headers: X-PINGOTHER
  7. Access-Control-Max-Age: 1728000


需要预检的复杂跨域请求 的请求头与响应头信息(PUTDELETE 以及 其他类型如 application/jsonPOST 请求)。
在发送AJAX请求之前,浏览器会先发送一个 OPTIONS 请求(称为 preflighted 请求)到这个 URL 上,询问目标服务器是否接受:

  1. OPTIONS /path/to/resource HTTP/1.1
  2. Host: bar.com
  3. Origin: http://my.com
  4. Access-Control-Request-Method: POST

服务器必须响应并明确指出允许的Method:

  1. HTTP/1.1 200 OK
  2. Access-Control-Allow-Origin: http://my.com
  3. Access-Control-Allow-Methods: POST, GET, PUT, OPTIONS
  4. Access-Control-Max-Age: 86400

浏览器确认服务器响应的 Access-Control-Allow-Methods 头确实包含将要发送的 AJAX 请求的 Method,才会继续发送 AJAX,否则,抛出一个错误。

  1. // AJAX Prefilght Request Header
  2. OPTIONS /resources/post-here/ HTTP/1.1
  3. Host: www.google.com
  4. Origin: http://www.example.com
  5. Access-Control-Request-Method: POST
  6. Access-Control-Request-Headers: X-PINGOTHER
  7. // AJAX Response Header for Preflight Request
  8. HTTP/1.1 200 OK
  9. Access-Control-Allow-Origin: http://www.example.com
  10. Access-Control-Allow-Methods: POST, GET, OPTIONS
  11. Access-Control-Allow-Headers: X-PINGOTHER
  12. Access-Control-Max-Age: 1728000
  13. // AJAX Request after Passed Preflight
  14. ...

解释

CORS 机制默认不发送 cookieHTTP 认证信息,除非在 Ajax 请求中打开 withCredentials 属性。

  1. var request = new XMLHttpRequest();
  2. request.withCredentials = true;

同时,服务器返回 HTTP 头信息时,也必须打开 Access-Control-Allow-Credentials 选项。否则,浏览器会忽略服务器返回的回应。

Access-Control-Allow-Credentials: true

由于整个过程都是浏览器自动后台完成,不用用户参与,所以对于开发者来说,使用 Ajax 跨域请求与同域请求没有区别,代码完全一样。但是,这需要服务器的支持,所以在使用 CORS 之前,要查看一下所请求的网站是否支持。


方案四:document.domain 跨子域

document.domain 属性可写,但不能设置为当前 URL 中不包含的域,通过设置 domain 属性相同可实现框架间不同子域页面的 JavaScript 通信

  1. // example.com
  2. document.domain = 'example.com';
  3. // b.example.com as a frame in example.com
  4. document.domain = 'example.com';
  5. // 可以使用程序从 example.com 访问
  6. do sth Cross-domain

document.domain 只能往上级域名方向设置,设置为非上级域名会报错,不能设置为顶级域名

  1. // a.example.com
  2. document.domain = 'example.com'; // 'example.com'
  3. document.domian = 'b.example.com'; // 'Uncaught DOMException: ...'

方案五:window.name 实现跨域

利用的机制是框架间的 window.name 共享机制,利用 window.name 来传输数据。

此方法,也需要两个不同域的页面相互配合。

链接:window.name 实现跨域请求


方案六:window.postMessage 方法跨域

window.postMessage(message,targetOrigin) 方法是 HTML5 新引进的特性,可以使用它来向其它的 window 对象发送消息,无论这个 window 对象是属于同源或不同源。

IE8+ 已支持。

此方法也需要两个不同域的页面相互配合!

链接:HTML5 postMessage解决跨域、跨窗口消息传递

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注