本文介紹如何在 ASP.NET Core 的應用程式中啟用 CORS。 瀏覽器安全可以防止網頁向其他域發送請求,而不是為網頁提供服務。 此限制稱為相同源策略。 同一源策略可防止惡意站點讀取另一個站點中的敏感數據。 有時,你可能想要允許其他站點對你的應用進行跨域請求。 有關詳細信息,請參閱MOZILL ...
本文介紹如何在 ASP.NET Core 的應用程式中啟用 CORS。
瀏覽器安全可以防止網頁向其他域發送請求,而不是為網頁提供服務。 此限制稱為相同源策略。 同一源策略可防止惡意站點讀取另一個站點中的敏感數據。 有時,你可能想要允許其他站點對你的應用進行跨域請求。 有關詳細信息,請參閱MOZILLA CORS 一文。
跨源資源共用(CORS):
- 是一種 W3C 標準,可讓伺服器放寬相同的源策略。
- 不是一項安全功能,CORS 放寬 security。 API 不能通過允許 CORS 來更安全。 有關詳細信息,請參閱CORS 的工作原理。
- 允許伺服器明確允許一些跨源請求,同時拒絕其他請求。
- 比早期的技術(如JSONP)更安全且更靈活。
同一原點
如果兩個 Url 具有相同的方案、主機和埠(RFC 6454),則它們具有相同的源。
這兩個 Url 具有相同的源:
https://example.com/foo.html
https://example.com/bar.html
這些 Url 的起源不同於前兩個 Url:
https://example.net
– 個不同的域https://www.example.com/foo.html
– 個不同的子域http://example.com/foo.html
– 個不同的方案https://example.com:9000/foo.html
– 個不同埠
比較來源時,Internet Explorer 不會考慮該埠。
具有命名策略和中間件的 CORS
CORS 中間件處理跨域請求。 以下代碼通過指定源為整個應用啟用 CORS:
public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; } readonly string MyAllowSpecificOrigins = "_myAllowSpecificOrigins"; public IConfiguration Configuration { get; } public void ConfigureServices(IServiceCollection services) { services.AddCors(options => { options.AddPolicy(MyAllowSpecificOrigins, builder => { builder.WithOrigins("http://example.com", "http://www.contoso.com"); }); }); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseHsts(); } app.UseCors(MyAllowSpecificOrigins); app.UseHttpsRedirection(); app.UseMvc(); } }
前面的代碼:
- 將策略名稱設置為 "_myAllowSpecificOrigins"。 策略名稱為任意名稱。
- 調用 UseCors 擴展方法,這將啟用 CORS。
- 使用lambda 表達式調用 AddCors。 Lambda 採用 @no__t 0 對象。 本文稍後將介紹配置選項,如
WithOrigins
。
@No__t-0 方法調用將 CORS 服務添加到應用的服務容器:
public void ConfigureServices(IServiceCollection services) { services.AddCors(options => { options.AddPolicy(MyAllowSpecificOrigins, builder => { builder.WithOrigins("http://example.com", "http://www.contoso.com"); }); }); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); }
有關詳細信息,請參閱本文檔中的CORS 策略選項。
@No__t-0 方法可以鏈接方法,如以下代碼所示:
public void ConfigureServices(IServiceCollection services) { services.AddCors(options => { options.AddPolicy(MyAllowSpecificOrigins, builder => { builder.WithOrigins("http://example.com", "http://www.contoso.com") .AllowAnyHeader() .AllowAnyMethod(); }); }); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); }
註意: URL不得包含尾隨斜杠(/
)。 如果 URL 以 /
終止,比較將返回 false
,並且不返回任何標頭。
將 CORS 策略應用到所有終結點
以下代碼通過 CORS 中間件將 CORS 策略應用到所有應用終結點:
public void Configure(IApplicationBuilder app, IHostingEnvironment env) { // Preceding code ommitted. app.UseRouting(); app.UseCors(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); // Following code ommited. }
警告
通過終結點路由,CORS 中間件必須配置為在對 @no__t 和 UseEndpoints
的調用之間執行。 配置不正確將導致中間件停止正常運行。
請參閱在 Razor Pages、控制器和操作方法中啟用 cors,以在頁面/控制器/操作級別應用 cors 策略。
有關測試上述代碼的說明,請參閱測試 CORS 。
使用終結點路由啟用 Cors
使用終結點路由,可以根據每個終結點啟用 CORS,使用 @no__t 的擴展方法集。
app.UseEndpoints(endpoints => { endpoints.MapGet("/echo", async context => context.Response.WriteAsync("echo")) .RequireCors("policy-name"); });
同樣,也可以為所有控制器啟用 CORS:
app.UseEndpoints(endpoints => { endpoints.MapControllers().RequireCors("policy-name"); });
使用屬性啟用 CORS
@No__t-1EnableCors @ no__t屬性提供了一種用於全局應用 CORS 的替代方法。 @No__t-0 特性可為選定的終結點(而不是所有終結點)啟用 CORS。
使用 @no__t 指定預設策略,並 [EnableCors("{Policy String}")]
指定策略。
@No__t-0 特性可應用於:
- Razor 頁
PageModel
- 控制器
- 控制器操作方法
您可以將不同的策略應用於控制器/頁面模型/操作,[EnableCors]
屬性。 將 [EnableCors]
屬性應用於控制器/頁面模型/操作方法,併在中間件中啟用 CORS 時,將應用這兩種策略。 建議不要結合策略。 使用同一個應用中的 [EnableCors]
特性或中間件。
下麵的代碼將不同的策略應用於每個方法:
[Route("api/[controller]")] [ApiController] public class WidgetController : ControllerBase { // GET api/values [EnableCors("AnotherPolicy")] [HttpGet] public ActionResult<IEnumerable<string>> Get() { return new string[] { "green widget", "red widget" }; } // GET api/values/5 [EnableCors] // Default policy. [HttpGet("{id}")] public ActionResult<string> Get(int id) { switch (id) { case 1: return "green widget"; case 2: return "red widget"; default: return NotFound(); } } }
以下代碼創建 CORS 預設策略和名為 "AnotherPolicy"
的策略:
public class StartupMultiPolicy { public StartupMultiPolicy(IConfiguration configuration) { Configuration = configuration; } public IConfiguration Configuration { get; } public void ConfigureServices(IServiceCollection services) { services.AddCors(options => { options.AddDefaultPolicy( builder => { builder.WithOrigins("http://example.com", "http://www.contoso.com"); }); options.AddPolicy("AnotherPolicy", builder => { builder.WithOrigins("http://www.contoso.com") .AllowAnyHeader() .AllowAnyMethod(); }); }); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseHsts(); } app.UseHttpsRedirection(); app.UseMvc(); } }
禁用 CORS
@No__t-1DisableCors @ no__t-2屬性對控制器/頁模型/操作禁用 CORS。
CORS 策略選項
本部分介紹可在 CORS 策略中設置的各種選項:
Startup.ConfigureServices
中調用 AddPolicy。 對於某些選項,最好先閱讀CORS 如何工作部分。
設置允許的來源
AllowAnyOrigin – 允許所有來源的 CORS 請求和任何方案(http
或 https
)。 AllowAnyOrigin
不安全,因為任何網站都可以嚮應用程式發出跨域請求。
備註
指定 @no__t 0 和 @no__t 為不安全配置,可能導致跨站點請求偽造。 使用這兩種方法配置應用時,CORS 服務將返回無效的 CORS 響應。
AllowAnyOrigin
會影響預檢請求和 @no__t 標頭。 有關詳細信息,請參閱預檢請求部分。
SetIsOriginAllowedToAllowWildcardSubdomains – 將策略的 @no__t 設置為在評估是否允許源時允許源與配置的通配符域匹配的函數。
options.AddPolicy("AllowSubdomain", builder => { builder.WithOrigins("https://*.example.com") .SetIsOriginAllowedToAllowWildcardSubdomains(); });
設置允許的 HTTP 方法
- 允許任何 HTTP 方法:
- 影響預檢請求和 @no__t 0 標頭。 有關詳細信息,請參閱預檢請求部分。
設置允許的請求標頭
若要允許在 CORS 請求中發送特定標頭(稱為作者請求標頭),請調用 WithHeaders 並指定允許的標頭:
options.AddPolicy("AllowHeaders", builder => { builder.WithOrigins("http://example.com") .WithHeaders(HeaderNames.ContentType, "x-custom-header"); });
若要允許所有作者請求標頭,請調用 AllowAnyHeader:
options.AddPolicy("AllowAllHeaders", builder => { builder.WithOrigins("http://example.com") .AllowAnyHeader(); });
此設置會影響預檢請求和 @no__t 0 標頭。 有關詳細信息,請參閱預檢請求部分。
僅當在 Access-Control-Request-Headers
中發送的標頭與 WithHeaders
中指定的標頭完全匹配時,才可以使用 CORS 中間件策略與 WithHeaders
指定的特定標頭匹配。
例如,考慮按如下方式配置的應用:
app.UseCors(policy => policy.WithHeaders(HeaderNames.CacheControl));
CORS 中間件使用以下請求標頭拒絕預檢請求,因為 WithHeaders
中未列出 Content-Language
(HeaderNames):
Access-Control-Request-Headers: Cache-Control, Content-Language
應用返回200 OK響應,但不會向後發送 CORS 標頭。 因此,瀏覽器不會嘗試跨域請求。
設置公開的響應標頭
預設情況下,瀏覽器不會嚮應用程式公開所有的響應標頭。 有關詳細信息,請參閱W3C 跨域資源共用(術語):簡單的響應標頭。
預設情況下可用的響應標頭包括:
Cache-Control
Content-Language
Content-Type
Expires
Last-Modified
Pragma
CORS 規範將這些標頭稱為簡單的響應標頭。 若要使其他標頭可用於應用程式,請調用 WithExposedHeaders:
options.AddPolicy("ExposeResponseHeaders", builder => { builder.WithOrigins("http://example.com") .WithExposedHeaders("x-custom-header"); });
跨域請求中的憑據
憑據需要在 CORS 請求中進行特殊處理。 預設情況下,瀏覽器不會使用跨域請求發送憑據。 憑據包括 cookie 和 HTTP 身份驗證方案。 若要使用跨域請求發送憑據,客戶端必須將 XMLHttpRequest.withCredentials
設置為 true
。
直接使用 @no__t:
var xhr = new XMLHttpRequest(); xhr.open('get', 'https://www.example.com/api/test'); xhr.withCredentials = true;
使用 jQuery:
$.ajax({ type: 'get', url: 'https://www.example.com/api/test', xhrFields: { withCredentials: true } });
使用提取 API:
fetch('https://www.example.com/api/test', { credentials: 'include' });
伺服器必須允許憑據。 若要允許跨域憑據,請調用 AllowCredentials:
options.AddPolicy("AllowCredentials", builder => { builder.WithOrigins("http://example.com") .AllowCredentials(); });
HTTP 響應包含一個 @no__t 0 的標頭,該標頭通知瀏覽器伺服器允許跨源請求的憑據。
如果瀏覽器發送憑據,但響應不包含有效的 @no__t 0 標頭,則瀏覽器不會嚮應用程式公開響應,而且跨源請求會失敗。
允許跨域憑據會帶來安全風險。 另一個域中的網站可以代表用戶將登錄用戶的憑據發送給該應用程式,而無需用戶的知識。
CORS 規範還指出,如果 @no__t 標頭存在,則將源設置為 "*"
(所有源)是無效的。
預檢請求
對於某些 CORS 請求,瀏覽器會在發出實際請求之前發送其他請求。 此請求稱為預檢請求。 如果滿足以下條件,瀏覽器可以跳過預檢請求:
- 請求方法為 GET、HEAD 或 POST。
- 應用未設置
Accept
、Accept-Language
、Content-Language
、Content-Type
或 @no__t 為的請求標頭。 - @No__t 的標頭(如果已設置)具有以下值之一:
application/x-www-form-urlencoded
multipart/form-data
text/plain
為客戶端請求設置的請求標頭上的規則適用於應用通過調用 @no__t @no__t 對象上的的標頭。 CORS 規範調用這些標頭作者請求標頭。 規則不適用於瀏覽器可以設置的標頭,如 User-Agent
、Host
或 Content-Length
。
下麵是預檢請求的示例:
OPTIONS https://myservice.azurewebsites.net/api/test HTTP/1.1
Accept: */*
Origin: https://myclient.azurewebsites.net
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: accept, x-my-custom-header
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; WOW64; Trident/6.0)
Host: myservice.azurewebsites.net
Content-Length: 0
預航班請求使用 HTTP OPTIONS 方法。 它包括兩個特殊標頭:
Access-Control-Request-Method
:將用於實際請求的 HTTP 方法。Access-Control-Request-Headers
:應用在實際請求上設置的請求標頭的列表。 如前文所述,這不包含瀏覽器設置的標頭,如User-Agent
。
CORS 預檢請求可能包括一個 @no__t 0 標頭,該標頭向伺服器指示與實際請求一起發送的標頭。
若要允許特定標頭,請調用 WithHeaders:
options.AddPolicy("AllowHeaders", builder => { builder.WithOrigins("http://example.com") .WithHeaders(HeaderNames.ContentType, "x-custom-header"); });
若要允許所有作者請求標頭,請調用 AllowAnyHeader:
options.AddPolicy("AllowAllHeaders", builder => { builder.WithOrigins("http://example.com") .AllowAnyHeader(); });
瀏覽器的設置方式並不完全一致 Access-Control-Request-Headers
。 如果將標頭設置為 @no__t 0 (或使用 AllowAnyHeader)以外的任何內容,則至少應包含 Accept
、Content-Type
和 @no__t,以及要支持的任何自定義標頭。
下麵是針對預檢請求的示例響應(假定伺服器允許該請求):
HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Length: 0
Access-Control-Allow-Origin: https://myclient.azurewebsites.net
Access-Control-Allow-Headers: x-my-custom-header
Access-Control-Allow-Methods: PUT
Date: Wed, 20 May 2015 06:33:22 GMT
響應包括一個 @no__t 0 標頭,該標頭列出了允許的方法,還可以選擇一個 @no__t 標頭,其中列出了允許的標頭。 如果預檢請求成功,則瀏覽器發送實際請求。
如果預檢請求被拒絕,應用將返回200 OK響應,但不會向後發送 CORS 標頭。 因此,瀏覽器不會嘗試跨域請求。
設置預檢過期時間
@No__t 的標頭指定可緩存對預檢請求的響應的時間長度。 若要設置此標頭,請調用 SetPreflightMaxAge:
options.AddPolicy("SetPreflightExpiration", builder => { builder.WithOrigins("http://example.com") .SetPreflightMaxAge(TimeSpan.FromSeconds(2520)); });
CORS 如何工作
本部分介紹 HTTP 消息級別的CORS請求中發生的情況。
- CORS不是一種安全功能。 CORS 是一種 W3C 標準,可讓伺服器放寬相同的源策略。
- 例如,惡意執行組件可能會對站點使用阻止跨站點腳本(XSS) ,並對啟用了 CORS 的站點執行跨站點請求,以竊取信息。
- API 不能通過允許 CORS 來更安全。
- 它由客戶端(瀏覽器)來強制執行 CORS。 伺服器執行請求並返迴響應,這是返回錯誤並阻止響應的客戶端。 例如,以下任何工具都將顯示伺服器響應:
- Fiddler
- Postman
- .NET HttpClient
- Web 瀏覽器,方法是在地址欄中輸入 URL。
- 它由客戶端(瀏覽器)來強制執行 CORS。 伺服器執行請求並返迴響應,這是返回錯誤並阻止響應的客戶端。 例如,以下任何工具都將顯示伺服器響應:
- 這是一種方法,使伺服器能夠允許瀏覽器執行跨源XHR或獲取 API請求,否則將被禁止。
- 瀏覽器(不含 CORS)不能執行跨域請求。 在 CORS 之前,使用JSONP來繞過此限制。 JSONP 不使用 XHR,它使用
<script>
標記接收響應。 允許跨源載入腳本。
- 瀏覽器(不含 CORS)不能執行跨域請求。 在 CORS 之前,使用JSONP來繞過此限制。 JSONP 不使用 XHR,它使用
CORS 規範介紹了幾個新的 HTTP 標頭,它們啟用了跨域請求。 如果瀏覽器支持 CORS,則會自動為跨域請求設置這些標頭。 若要啟用 CORS,無需自定義 JavaScript 代碼。
下麵是一個跨源請求的示例。 @No__t 0 標頭提供發出請求的站點的域。 @No__t 的標頭是必需的,並且必須與主機不同。
GET https://myservice.azurewebsites.net/api/test HTTP/1.1
Referer: https://myclient.azurewebsites.net/
Accept: */*
Accept-Language: en-US
Origin: https://myclient.azurewebsites.net
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; WOW64; Trident/6.0)
Host: myservice.azurewebsites.net
如果伺服器允許該請求,則會在響應中設置 @no__t 的標頭。 此標頭的值可以與請求中的 @no__t 0 標頭匹配,也可以是通配符值 "*"
,表示允許任何源:
HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Type: text/plain; charset=utf-8
Access-Control-Allow-Origin: https://myclient.azurewebsites.net
Date: Wed, 20 May 2015 06:27:30 GMT
Content-Length: 12
Test message
如果響應不包括 @no__t 的標頭,則跨域請求會失敗。 具體而言,瀏覽器不允許該請求。 即使伺服器返回成功的響應,瀏覽器也不會將響應提供給客戶端應用程式。
測試 CORS
測試 CORS:
public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseHsts(); } // Shows UseCors with CorsPolicyBuilder. app.UseCors(builder => { builder.WithOrigins("http://example.com", "http://www.contoso.com", "https://localhost:44375", "https://localhost:5001"); }); app.UseHttpsRedirection(); app.UseMvc(); }
警告
WithOrigins("https://localhost:<port>");
應僅用於測試類似於下載示例代碼的示例應用。
- 創建 web 應用項目(Razor Pages 或 MVC)。 該示例使用 Razor Pages。 可以在與 API 項目相同的解決方案中創建 web 應用。
- 將以下突出顯示的代碼添加到索引 cshtml文件中:
@page @model IndexModel @{ ViewData["Title"] = "Home page"; } <div class="text-center"> <h1 class="display-4">CORS Test</h1> </div> <div> <input type="button" value="Test" onclick="requestVal('https://<web app>.azurewebsites.net/api/values')" /> <span id='result'></span> </div> <script> function requestVal(uri) { const resultSpan = document.getElementById('result'); fetch(uri) .then(response => response.json()) .then(data => resultSpan.innerText = data) .catch(error => resultSpan.innerText = 'See F12 Console for error'); } </script>
-
在上面的代碼中,將
url: 'https://<web app>.azurewebsites.net/api/values/1',
替換為已部署應用的 URL。 -
部署 API 項目。 例如,部署到 Azure。
-
從桌面運行 Razor Pages 或 MVC 應用,然後單擊 "測試" 按鈕。 使用 F12 工具查看錯誤消息。
-
從 @no__t 中刪除 localhost 源,並部署應用。 或者,使用其他埠運行客戶端應用。 例如,在 Visual Studio 中運行。
-
與客戶端應用程式進行測試。 CORS 故障返回一個錯誤,但錯誤消息不能用於 JavaScript。 使用 F12 工具中的 "控制台" 選項卡查看錯誤。 根據瀏覽器,你會收到類似於以下內容的錯誤(在 F12 工具控制臺中):
-
使用 Microsoft Edge:
SEC7120: [CORS] 源
https://localhost:44375
在 @no__t 上找不到跨源資源的訪問控制允許源響應標頭中的https://localhost:44375
-
使用 Chrome:
對源
https://localhost:44375
https://webapi.azurewebsites.net/api/values/1
的 XMLHttpRequest 的訪問已被 CORS 策略阻止:請求的資源上沒有 "訪問控制-允許" 標頭。
-
可以使用工具(如Fiddler或Postman)測試啟用 CORS 的終結點。 使用工具時,@no__t 的標頭指定的請求源必須與接收請求的主機不同。 如果請求不是基於 Origin
標頭的值跨域的,則:
- 不需要 CORS 中間件來處理請求。
- 不會在響應中返回 CORS 標頭。