2014年1月16日,W3C的Web應(yīng)用工作組(Web Applications Working Group)和Web應(yīng)用安全工作組(Web AppSec)聯(lián)合發(fā)布了跨源資源共享(Cross-Origin Resource Sharing)的W3C正式推薦標(biāo)準(zhǔn)(W3C Recommendation)。該標(biāo)準(zhǔn)定義了在必須訪問跨域資源時(shí),瀏覽器與服務(wù)端應(yīng)該如何溝通,它提供一種機(jī)制,允許客戶端(如瀏覽器)對(duì)非源站點(diǎn)的資源發(fā)出訪問請(qǐng)求。所有提供跨源資源請(qǐng)求的API都可以使用本規(guī)范中定義的算法。
出于安全性的考慮,用戶代理(如瀏覽器)通常拒絕跨站的訪問請(qǐng)求,但這會(huì)限制運(yùn)行在用戶代理的Web應(yīng)用通過Ajax或者其他機(jī)制從另一個(gè)站點(diǎn)訪問資源、獲取數(shù)據(jù)??缭促Y源共享(CORS)擴(kuò)充了這個(gè)模型,通過使用自定義的HTTP響應(yīng)頭部(HTTP Response Header),通知瀏覽器資源可能被哪些跨源站點(diǎn)以何種HTTP方法獲得。例如,瀏覽器在訪問 http://example.com 站點(diǎn)的Web應(yīng)用時(shí),Web應(yīng)用如果需要跨站訪問另一站點(diǎn)的資源 http://hello-world.example,就需要使用該標(biāo)準(zhǔn)。http://hello-world.example 在HTTP的響應(yīng)頭部中定義 Access-Control-Allow-Origin: http://example.org,通知瀏覽器允許 http://example.org 跨源從 http://hello-world.example上獲取資源。
CORS 圖解
過程說明:
1 瀏覽器發(fā)出跨域訪問請(qǐng)求,request header 攜帶 origin
2 服務(wù)器收到請(qǐng)求,服務(wù)器返回response header
3 瀏覽區(qū)檢查reponse header ,如果Access-Control-Allow-Origin : 包含request header的Origin,那么瀏覽器認(rèn)為這是安全操作,允許操作服務(wù)端返回的數(shù)據(jù),否則瀏覽器認(rèn)為非同源數(shù)據(jù),不允許操作。
目前主流的瀏覽器基本都支持CORS規(guī)范,所以實(shí)現(xiàn)CORS跨域訪問的關(guān)鍵就是Server 端返回的respone中添加 Access-Control-Allow-Origin:對(duì)應(yīng)的請(qǐng)求origin.
Web API 如何實(shí)現(xiàn)跨域請(qǐng)求:
服務(wù)端代碼
類說明:
CorsAttribute:請(qǐng)求跨域的標(biāo)記
CorsMessageHandler:
攔截請(qǐng)求,確認(rèn)請(qǐng)求是否允許跨域,如果允許,那么給response header 添加跨域請(qǐng)求 Access-Control-Allow-Origin,否則返回
{
"Message": "Cross-origin request denied"
}
代碼:
using System;using System.Collections.Generic;using System.Linq;using System.Net.Http;using System.Threading;using System.Threading.Tasks;using System.Web;using System.Web.Http;using System.Web.Http.Controllers;using System.Net;using System.Diagnostics; namespace Hbb0b0.MVC.CORS { /// <summary> /// 跨域?qū)傩?nbsp; /// 此處參考 /// asp.net webapi 2 /// /// </summary> public class CorsAttribute : Attribute { /// <summary> /// 允許的站點(diǎn)列表 /// </summary> public Uri[] AllowOrigins { get; private set; } /// <summary> /// 錯(cuò)誤提示 /// </summary> public string ErrorMessage { get; set; } /// <summary> /// 構(gòu)造函數(shù) /// </summary> /// <param name="allowOrigins"></param> public CorsAttribute(params string[] allowOrigins) { this.AllowOrigins = (allowOrigins ?? new string[0]).Select(p => new Uri(p)).ToArray(); } /// <summary> /// 獲取請(qǐng)求頭信息,判斷是否請(qǐng)求被允許跨域 /// </summary> /// <param name="request"></param> /// <param name="headers"></param> /// <returns></returns> public bool TryEvaluate(HttpRequestMessage request, out IDictionary<string, string> headers) { headers = null; string origin = request.Headers.GetValues(HttpHeaderKeys.ORIGIN).FirstOrDefault(); Uri originUrl = new Uri(origin); if (this.AllowOrigins.Contains(originUrl)) { headers = this.GenerateResposeHeaders(request); return true; } this.ErrorMessage = "Cross-origin request denied"; return false; } /// <summary> /// 添加跨域許可請(qǐng)求頭 /// </summary> /// <param name="request"></param> /// <returns></returns> private IDictionary<string, string> GenerateResposeHeaders(HttpRequestMessage request) { string origin = request.Headers.GetValues("Origin").First(); Dictionary<string, string> headers = new Dictionary<string, string>(); headers.Add(HttpHeaderKeys.ACCESS_CONTROL_ALLOW_ORIGIN, origin); if (request.IsPreflightRequest()) { headers.Add(HttpHeaderKeys.ACCESS_CONTROL_ALLOW_ORIGIN, "*"); string requestHeaders = request.Headers.GetValues(HttpHeaderKeys.ACCESS_CONTROL_ALLOW_REQUEST_HEADERS).FirstOrDefault(); if (!string.IsNullOrEmpty(requestHeaders)) { headers.Add(HttpHeaderKeys.ACCESS_CONTROL_ALLOW_REQUEST_HEADERS, requestHeaders); } } return headers; } } /// <summary> /// Http Header keys /// </summary> public class HttpHeaderKeys { public const string ORIGIN = "Origin"; public const string ACCESS_CONTROL_ALLOW_ORIGIN = "Access-Control-Allow-Origin"; public const string ACCESS_CONTROL_ALLOW_REQUEST_METHOD = "Access-Control-Allow-Request-Methods"; public const string ACCESS_CONTROL_ALLOW_REQUEST_HEADERS = "Access-Control-Allow-Request-Headers"; } /// <summary> /// 判斷是否是非簡單跨域請(qǐng)求擴(kuò)展方法 /// </summary> public static class HttpRequestMessageExtensions { public static bool IsPreflightRequest(this HttpRequestMessage request) { return request.Method == HttpMethod.Options && request.Headers.GetValues(HttpHeaderKeys.ORIGIN).Any() && request.Headers.GetValues(HttpHeaderKeys.ACCESS_CONTROL_ALLOW_REQUEST_METHOD).Any(); } } public class CorsMessageHandler : DelegatingHandler { protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { //Debugger.Break(); HttpMethod originalMethod = request.Method; bool isPreflightRequest = request.IsPreflightRequest(); if (isPreflightRequest) { string method = request.Headers.GetValues(HttpHeaderKeys.ACCESS_CONTROL_ALLOW_REQUEST_HEADERS).FirstOrDefault(); request.Method = new HttpMethod(method); } HttpConfiguration config = request.GetConfiguration(); HttpControllerDescriptor description = config.Services.GetHttpControllerSelector().SelectController(request); HttpControllerContext context = new HttpControllerContext(request.GetConfiguration(), request.GetRouteData(), request) { ControllerDescriptor = description }; HttpActionDescriptor actionDescriptor = config.Services.GetActionSelector().SelectAction(context); CorsAttribute corsAttribute = actionDescriptor.GetCustomAttributes<CorsAttribute>().FirstOrDefault(); if (corsAttribute == null) { return base.SendAsync(request, cancellationToken); } IDictionary<string, string> headers; request.Method = originalMethod; HttpResponseMessage response = null; //判斷請(qǐng)求是否被授權(quán) bool authorized = corsAttribute.TryEvaluate(request, out headers); //處理非簡單跨域請(qǐng)求 if (isPreflightRequest) { if (authorized) { response = new HttpResponseMessage(HttpStatusCode.OK); } } //簡單跨域請(qǐng)求 else { //如果授權(quán),返回請(qǐng)求資源 if (authorized) { response = base.SendAsync(request, cancellationToken).Result; } //非授權(quán),返回錯(cuò)誤信息 else { response = request.CreateErrorResponse(HttpStatusCode.BadRequest, corsAttribute.ErrorMessage); } } //添加header if (headers!=null) { foreach (var item in headers) { response.Headers.Add(item.Key, item.Value); } } return Task.FromResult<HttpResponseMessage>(response); } } }
客戶端代碼:
@{ Layout = null; } <!DOCTYPE html> <html><head> <meta name="viewport" content="width=device-width" /> <title>View</title> <script type="text/javascript" src="~/Scripts/jquery-1.10.2.js"></script> <script type="text/javascript"> $(function () { $.ajax({ type: "POST", async: false, url: "http://localhost/Hbb0b0.mvc.API/api/StudentAPI/", beforeSend: function (xmlHttpRequest) { //xmlHttpRequest.setRequestHeader("Origin", "http://hbb0b0notebook:51929/"); }, success: function (data) { //alert(data.name); console.log(data); getStudentList(data); }, error: function () { alert('fail'); } }); }); function getStudentList(list) { console.debug("GetStudenListCors", list); $.each(list, function (index, student) { var html = "<ul>"; html += "<li> name:" + student.Name + "</li>" html += "</ul>"; $("#divStudentList").append(html); }); } </script> </head><body> <div id="divStudentList"> </div> </body></html>
運(yùn)行結(jié)果
Request Header:
Response header:
結(jié)論
CORS 作為W3C官方正式的跨域資源訪問方案,不像JSONP 是一種臨時(shí)方案。
CORS 不對(duì)請(qǐng)求類型做限制,get, post 都支持,JSONPzhi只支持get. 所以 CORS比JSONP更通用,更是主流的跨域資源訪問請(qǐng)求解決方案。
http://www.cnblogs.com/hbb0b0/p/7189709.html