跨域访问之CORS

时间:2022-08-28 22:45:24

CORS:定义

 

2014年1月16日,W3C的Web应用工作组(Web Applications Working Group)Web应用安全工作组(Web AppSec)联合发布了跨源资源共享(Cross-Origin Resource Sharing)的W3C正式推荐标准(W3C Recommendation)。该标准定义了在必须访问跨域资源时,浏览器与服务端应该如何沟通,它提供一种机制,允许客户端(如浏览器)对非源站点的资源发出访问请求。所有提供跨源资源请求的API都可以使用本规范中定义的算法。

出于安全性的考虑,用户代理(如浏览器)通常拒绝跨站的访问请求,但这会限制运行在用户代理的Web应用通过Ajax或者其他机制从另一个站点访问资源、获取数据。跨源资源共享(CORS)扩充了这个模型,通过使用自定义的HTTP响应头部(HTTPResponse Header),通知浏览器资源可能被哪些跨源站点以何种HTTP方法获得。例如,浏览器在访问 http://example.com 站点的Web应用时,Web应用如果需要跨站访问另一站点的资源 http://hello-world.example,就需要使用该标准。http://hello-world.example 在HTTP的响应头部中定义 Access-Control-Allow-Origin:http://example.org,通知浏览器允许 http://example.org 跨源从 http://hello-world.example上获取资源。

 

 

 

CORS 图解

跨域访问之CORS

 

 

过程说明:

1 浏览器发出跨域访问请求,request header 携带 origin

2 服务器收到请求,服务器返回response header

3 浏览区检查reponse header ,如果Access-Control-Allow-Origin: 包含request header的Origin,那么浏览器认为这是安全操作,允许操作服务端返回的数据,否则浏览器认为非同源数据,不允许操作。

 

 

目前主流的浏览器基本都支持CORS规范,所以实现CORS跨域访问的关键就是Server 端返回的respone中添加 Access-Control-Allow-Origin:对应的请求origin.

 

Web API 如何实现跨域请求:

 

服务端代码

 

类说明:

 

 

  1. CorsAttribute:请求跨域的标记

 

  1. CorsMessageHandler:

拦截请求,确认请求是否允许跨域,如果允许,那么给response header 添加跨域请求 Access-Control-Allow-Origin,否则返回

{

  "Message": "Cross-originrequest 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>

/// 跨域属性

/// 此处参考

/// asp.net webapi 2

///

/// </summary>

public class CorsAttribute : Attribute

{



/// <summary>

/// 允许的站点列表

/// </summary>

public Uri[] AllowOrigins

{

get;

private set;

}



/// <summary>

/// 错误提示

/// </summary>

public string ErrorMessage

{

get;

set;

}



/// <summary>

/// 构造函数

/// </summary>

/// <param name="allowOrigins"></param>

public CorsAttribute(params string[] allowOrigins)

{

this.AllowOrigins = (allowOrigins ?? new string[0]).Select(p => new Uri(p)).ToArray();

}





/// <summary>

/// 获取请求头信息,判断是否请求被允许跨域

/// </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>

/// 添加跨域许可请求头

/// </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>

/// 判断是否是非简单跨域请求扩展方法

/// </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;

//判断请求是否被授权

bool authorized = corsAttribute.TryEvaluate(request, out headers);



//处理非简单跨域请求

if (isPreflightRequest)

{

if (authorized)

{

response
= new HttpResponseMessage(HttpStatusCode.OK);

}



}

//简单跨域请求

else

{

//如果授权,返回请求资源

if (authorized)

{

response
= base.SendAsync(request, cancellationToken).Result;

}

//非授权,返回错误信息

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>

 

运行结果

跨域访问之CORS

 

 

Request Header:

 

跨域访问之CORS

 

Response header:

 跨域访问之CORS

跨域访问之CORS

 

 

结论

CORS 作为W3C官方正式的跨域资源访问方案,不像JSONP 是一种临时方案。

CORS 不对请求类型做限制,get, post 都支持,JSONPzhi只支持get. 所以 CORS比JSONP更通用,更是主流的跨域资源访问请求解决方案。

 

 

本文参考了<<asp.net  webapi 2  框架解密>>,此书的确是深入剖析asp.net 框架的好书。