共用方式為


在 ASP.NET Web API 2 中啟用跨原始來源要求

作者:Mike Wasson

此內容適用于舊版 .NET。 新的開發應該使用ASP.NET Core。 如需在 ASP.NET Core 中使用 Web API 和跨原始來源要求 (CORS) 的詳細資訊,請參閱:

瀏覽器安全性可防止網頁對另一個網域提出 AJAX 要求。 這項限制也稱為同源原則,可防止惡意網站從另一個網站讀取敏感性資料。 不過,有時候您可能會想要讓其他網站呼叫您的 Web API。

跨原始來源資源分享 (CORS) 是一種 W3C 標準,可讓伺服器放寬相同的原始來源原則。 使用 CORS,伺服器可以明確允許某些跨源要求,然而拒絕其他要求。 CORS 比先前的 JSONP技術更安全且更有彈性。 本教學課程說明如何在 Web API 應用程式中啟用 CORS。

教學課程中使用的軟體

簡介

本教學課程示範 ASP.NET Web API 中的 CORS 支援。 我們將從建立兩個 ASP.NET 專案開始,其中一個稱為 「WebService」,其中裝載 Web API 控制器,另一個稱為 「WebClient」,其會呼叫 WebService。 因為兩個應用程式裝載在不同的網域,所以從 WebClient 到 WebService 的 AJAX 要求是跨原始來源的要求。

顯示 Web 服務和 Web 用戶端

什麼是「相同原始來源」?

如果兩個 URL 具有相同的配置、主機和埠,則具有相同的來源。 (RFC 6454)

這兩個 URL 的原點相同:

  • http://example.com/foo.html
  • http://example.com/bar.html

這些 URL 的原點與前兩個 URL 不同:

  • http://example.net - 不同的網域
  • http://example.com:9000/foo.html - 不同的連接埠
  • https://example.com/foo.html - 不同的配置
  • http://www.example.com/foo.html - 不同的子網域

注意

Internet Explorer 不會在比較來源時考慮埠。

建立 WebService 專案

注意

本節假設您已經知道如何建立 Web API 專案。 如果沒有,請參閱使用 ASP.NET Web API消費者入門

  1. 啟動 Visual Studio 並建立新的ASP.NET Web 應用程式 (.NET Framework) 專案。

  2. 在 [ 新增 ASP.NET Web 應用程式 ] 對話方塊中,選取 [空白 專案範本]。 在 [新增資料夾和核心參考]底下,選取 [Web API ] 核取方塊。

    Visual Studio 中的 [新增 ASP.NET 專案] 對話方塊

  3. 使用下列程式碼新增名為 TestController 的 Web API 控制器:

    using System.Net.Http;
    using System.Web.Http;
    
    namespace WebService.Controllers
    {
        public class TestController : ApiController
        {
            public HttpResponseMessage Get()
            {
                return new HttpResponseMessage()
                {
                    Content = new StringContent("GET: Test message")
                };
            }
    
            public HttpResponseMessage Post()
            {
                return new HttpResponseMessage()
                {
                    Content = new StringContent("POST: Test message")
                };
            }
    
            public HttpResponseMessage Put()
            {
                return new HttpResponseMessage()
                {
                    Content = new StringContent("PUT: Test message")
                };
            }
        }
    }
    
  4. 您可以在本機執行應用程式,或部署至 Azure。 (針對本教學課程中的螢幕擷取畫面,應用程式會部署至 Azure App 服務 Web Apps.) 若要確認 Web API 正常運作,請流覽至 http://hostname/api/test/ ,其中主機名稱是您部署應用程式的網域。 您應該會看到回應文字「GET:測試訊息」。

    顯示測試訊息的網頁瀏覽器

建立 WebClient 專案

  1. 建立另一個ASP.NET Web 應用程式 (.NET Framework) 專案,然後選取MVC專案範本。 選擇性地選取[變更驗證>否驗證]。 您不需要本教學課程的驗證。

    Visual Studio 中 [新增 ASP.NET 專案] 對話方塊中的 MVC 範本

  2. 方案總管中,開啟Views/Home/Index.cshtml檔案。 將此檔案中的程式碼取代為下列專案:

    <div>
        <select id="method">
            <option value="get">GET</option>
            <option value="post">POST</option>
            <option value="put">PUT</option>
        </select>
        <input type="button" value="Try it" onclick="sendRequest()" />
        <span id='value1'>(Result)</span>
    </div>
    
    @section scripts {
    <script>
        // TODO: Replace with the URL of your WebService app
        var serviceUrl = 'http://mywebservice/api/test'; 
    
        function sendRequest() {
            var method = $('#method').val();
    
            $.ajax({
                type: method,
                url: serviceUrl
            }).done(function (data) {
                $('#value1').text(data);
            }).fail(function (jqXHR, textStatus, errorThrown) {
                $('#value1').text(jqXHR.responseText || textStatus);
            });
        }
    </script>
    }
    

    針對 serviceUrl 變數,請使用 WebService 應用程式的 URI。

  3. 在本機執行 WebClient 應用程式,或將其發佈至另一個網站。

當您按一下 [試用] 按鈕時,AJAX 要求會使用下拉式方塊中列出的 HTTP 方法提交至 WebService 應用程式, (GET、POST 或 PUT) 。 這可讓您檢查不同的跨原始來源要求。 目前,WebService 應用程式不支援 CORS,因此,如果您按一下按鈕,將會收到錯誤。

瀏覽器中的「試試看」錯誤

注意

如果您在Fiddler之類的工具中watch HTTP 流量,您會看到瀏覽器會傳送 GET 要求,而且要求成功,但 AJAX 呼叫會傳回錯誤。 請務必瞭解相同來源原則不會防止瀏覽器 傳送 要求。 相反地,它可防止應用程式看到 回應

顯示 Web 要求的 Fiddler Web 偵錯工具

啟用 CORS

現在,讓我們在 WebService 應用程式中啟用 CORS。 首先,新增 CORS NuGet 套件。 在 Visual Studio 的 [ 工具] 功能表中,選取 [NuGet 套件管理員],然後選取 [ 套件管理員主控台]。 在 [套件管理員主控台] 視窗中,輸入下列命令:

Install-Package Microsoft.AspNet.WebApi.Cors

此命令會安裝最新的套件並更新所有相依性,包括核心 Web API 程式庫。 -Version使用 旗標以特定版本為目標。 CORS 套件需要 Web API 2.0 或更新版本。

開啟 檔案App_Start/WebApiConfig.cs。 將下列程式碼新增至 WebApiConfig.Register 方法:

using System.Web.Http;
namespace WebService
{
    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            // New code
            config.EnableCors();

            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );
        }
    }
}

接下來,將 [EnableCors] 屬性新增至 TestController 類別:

using System.Net.Http;
using System.Web.Http;
using System.Web.Http.Cors;

namespace WebService.Controllers
{
    [EnableCors(origins: "http://mywebclient.azurewebsites.net", headers: "*", methods: "*")]
    public class TestController : ApiController
    {
        // Controller methods not shown...
    }
}

針對 origins 參數,請使用您部署 WebClient 應用程式的 URI。 這允許來自 WebClient 的跨原始來源要求,同時仍不允許所有其他跨網域要求。 稍後,我會更詳細地描述 [EnableCors] 的參數。

請勿在 點 URL 的結尾包含正斜線。

重新部署更新的 WebService 應用程式。 您不需要更新 WebClient。 現在,來自 WebClient 的 AJAX 要求應該會成功。 允許 GET、PUT 和 POST 方法。

顯示成功測試訊息的網頁瀏覽器

CORS 的運作方式

本節說明在 HTTP 訊息層級的 CORS 要求中會發生什麼情況。 請務必瞭解 CORS 的運作方式,讓您可以正確設定 [EnableCors] 屬性,並在預期情況下無法運作時進行疑難排解。

CORS 規格引進數個新的 HTTP 標頭,可啟用跨原始來源要求。 如果瀏覽器支援 CORS,它會針對跨原始來源要求自動設定這些標頭;您不需要在 JavaScript 程式碼中執行任何特殊動作。

以下是跨原始來源要求的範例。 「Origin」 標頭會提供發出要求之網站的網域。

GET http://myservice.azurewebsites.net/api/test HTTP/1.1
Referer: http://myclient.azurewebsites.net/
Accept: */*
Accept-Language: en-US
Origin: http://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

如果伺服器允許要求,它會設定 Access-Control-Allow-Origin 標頭。 此標頭的值符合 Origin 標頭,或 是萬用字元值 「*」,表示允許任何原始來源。

HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Type: text/plain; charset=utf-8
Access-Control-Allow-Origin: http://myclient.azurewebsites.net
Date: Wed, 05 Jun 2013 06:27:30 GMT
Content-Length: 17

GET: Test message

如果回應不包含 Access-Control-Allow-Origin 標頭,AJAX 要求會失敗。 具體而言,瀏覽器不允許要求。 即使伺服器傳回成功的回應,瀏覽器也不會將回應提供給用戶端應用程式。

預檢要求

對於某些 CORS 要求,瀏覽器會在傳送資源的實際要求之前,先傳送稱為「預檢要求」的額外要求。

如果下列條件成立,瀏覽器可以略過預檢要求:

  • 要求方法是 GET、HEAD 或 POST, 以及

  • 應用程式不會設定 Accept、Accept-Language、Content-Language、Content-Type 或 Last-Event-ID 以外的任何要求標頭, 以及

  • 如果設定) 為下列其中一項,則 Content-Type 標頭 (:

    • application/x-www-form-urlencoded
    • multipart/form-data
    • text/plain

要求標頭的相關規則會套用至應用程式在XMLHttpRequest物件上呼叫setRequestHeader所設定的標頭。 (CORS 規格會呼叫這些「作者要求標頭」。) 此規則不適用於 瀏覽器 可以設定的標頭,例如 User-Agent、Host 或 Content-Length。

以下是預檢要求範例:

OPTIONS http://myservice.azurewebsites.net/api/test HTTP/1.1
Accept: */*
Origin: http://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: 應用程式 在實際要求上設定的要求標頭清單。 再次 (,這不包含瀏覽器 set.)

以下是範例回應,假設伺服器允許要求:

HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Length: 0
Access-Control-Allow-Origin: http://myclient.azurewebsites.net
Access-Control-Allow-Headers: x-my-custom-header
Access-Control-Allow-Methods: PUT
Date: Wed, 05 Jun 2013 06:33:22 GMT

回應包含會列出允許方法的 Access-Control-Allow-Methods 標頭,以及選擇性地列出允許標頭的 Access-Control-Allow-Headers 標頭。 如果預檢要求成功,瀏覽器會傳送實際要求,如先前所述。

通常用來測試具有預檢 OPTIONS 要求的端點 (的工具,例如 FiddlerPostman) 預設不會傳送必要的 OPTIONS 標頭。 確認 Access-Control-Request-MethodAccess-Control-Request-Headers 標頭會隨著要求一起傳送,而且 OPTIONS 標頭會透過 IIS 連線到應用程式。

若要將 IIS 設定為允許 ASP.NET 應用程式接收及處理 OPTION 要求,請在 區段中將下列組態新增至應用程式的 web.config 檔案 <system.webServer><handlers>

<system.webServer>
  <handlers>
    <remove name="ExtensionlessUrlHandler-Integrated-4.0" />
    <remove name="OPTIONSVerbHandler" />
    <add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="*" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
  </handlers>
</system.webServer>

移除 OPTIONSVerbHandler 可防止 IIS 處理 OPTIONS 要求。 的取代 ExtensionlessUrlHandler-Integrated-4.0 可讓 OPTIONS 要求觸達應用程式,因為預設模組註冊只允許具有無擴充 URL 的 GET、HEAD、POST 和 DEBUG 要求。

[EnableCors] 的範圍規則

您可以針對應用程式中的所有 Web API 控制器啟用每個動作、每個控制器或全域的 CORS。

每個動作

若要啟用單一動作的 CORS,請在動作方法上設定 [EnableCors] 屬性。 下列範例僅啟用 方法的 GetItem CORS。

public class ItemsController : ApiController
{
    public HttpResponseMessage GetAll() { ... }

    [EnableCors(origins: "http://www.example.com", headers: "*", methods: "*")]
    public HttpResponseMessage GetItem(int id) { ... }

    public HttpResponseMessage Post() { ... }
    public HttpResponseMessage PutItem(int id) { ... }
}

每個控制器

如果您在控制器類別上設定 [EnableCors] ,則會套用至控制器上的所有動作。 若要停用動作的 CORS,請將 [DisableCors] 屬性新增至動作。 下列範例會針對除了 以外的 PutItem 每個方法啟用 CORS。

[EnableCors(origins: "http://www.example.com", headers: "*", methods: "*")]
public class ItemsController : ApiController
{
    public HttpResponseMessage GetAll() { ... }
    public HttpResponseMessage GetItem(int id) { ... }
    public HttpResponseMessage Post() { ... }

    [DisableCors]
    public HttpResponseMessage PutItem(int id) { ... }
}

全域

若要為應用程式中的所有 Web API 控制器啟用 CORS,請將 EnableCorsAttribute 實例傳遞至 EnableCors 方法:

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        var cors = new EnableCorsAttribute("www.example.com", "*", "*");
        config.EnableCors(cors);
        // ...
    }
}

如果您在多個範圍設定 屬性,優先順序為:

  1. 動作
  2. 控制器
  3. 全球

設定允許的來源

[EnableCors]屬性的origins參數會指定允許哪些來源存取資源。 此值是以逗號分隔的允許來源清單。

[EnableCors(origins: "http://www.contoso.com,http://www.example.com", 
    headers: "*", methods: "*")]

您也可以使用萬用字元值 「*」 來允許來自任何來源的要求。

先仔細考慮,再允許來自任何來源的要求。 這表示任何網站都可以對 Web API 進行 AJAX 呼叫。

// Allow CORS for all origins. (Caution!)
[EnableCors(origins: "*", headers: "*", methods: "*")]

設定允許的 HTTP 方法

[EnableCors]屬性的methods參數會指定允許哪些 HTTP 方法存取資源。 若要允許所有方法,請使用萬用字元值 「*」。 下列範例只允許 GET 和 POST 要求。

[EnableCors(origins: "http://www.example.com", headers: "*", methods: "get,post")]
public class TestController : ApiController
{
    public HttpResponseMessage Get() { ... }
    public HttpResponseMessage Post() { ... }
    public HttpResponseMessage Put() { ... }    
}

設定允許的要求標頭

本文稍早說明預檢要求如何包含 Access-Control-Request-Headers 標頭,並列出應用程式所設定的 HTTP 標頭, (所謂的「作者要求標頭」) 。 [EnableCors]屬性的headers參數會指定允許哪些作者要求標頭。 若要允許任何標頭,請將 標頭 設定為 「*」。 若要允許特定標頭,請將 標頭 設定為允許標頭的逗號分隔清單:

[EnableCors(origins: "http://example.com", 
    headers: "accept,content-type,origin,x-my-header", methods: "*")]

不過,瀏覽器在設定 Access-Control-Request-Header 的方式中並不完全一致。 例如,Chrome 目前包含「原點」。 FireFox 不包含標準標頭,例如 「Accept」,即使應用程式在腳本中設定它們也一樣。

如果您將標頭設定為 「*」 以外的任何 專案 ,您應該至少包含 「accept」、「content-type」 和 「origin」,以及您想要支援的任何自訂標頭。

設定允許的回應標頭

根據預設,瀏覽器不會將所有回應標頭公開給應用程式。 預設可用的回應標頭為:

  • Cache-Control
  • Content-Language
  • Content-Type
  • 到期 (Expires)
  • 上次修改時間
  • Pragma

CORS 規格會呼叫這些 簡單的回應標頭。 若要讓其他標頭可供應用程式使用,請設定[EnableCors]公開Headers參數。

在下列範例中 Get ,控制器的 方法會設定名為 'X-Custom-Header' 的自訂標頭。 根據預設,瀏覽器不會在跨原始來源要求中公開此標頭。 若要讓標頭可供使用,請在 公開的Headers中包含 'X-Custom-Header'。

[EnableCors(origins: "*", headers: "*", methods: "*", exposedHeaders: "X-Custom-Header")]
public class TestController : ApiController
{
    public HttpResponseMessage Get()
    {
        var resp = new HttpResponseMessage()
        {
            Content = new StringContent("GET: Test message")
        };
        resp.Headers.Add("X-Custom-Header", "hello");
        return resp;
    }
}

在跨原始來源要求中傳遞認證

認證需要 CORS 要求中的特殊處理。 根據預設,瀏覽器不會傳送任何具有跨原始來源要求的認證。 認證包括 Cookie 和 HTTP 驗證配置。 若要傳送具有跨原始來源要求的認證,用戶端必須將 XMLHttpRequest.withCredentials 設定為 true。

直接使用 XMLHttpRequest

var xhr = new XMLHttpRequest();
xhr.open('get', 'http://www.example.com/api/test');
xhr.withCredentials = true;

在 jQuery 中:

$.ajax({
    type: 'get',
    url: 'http://www.example.com/api/test',
    xhrFields: {
        withCredentials: true
    }

此外,伺服器必須允許認證。 若要允許 Web API 中的跨原始來源認證,請將[EnableCors]屬性上的SupportsCredentials屬性設定為 true:

[EnableCors(origins: "http://myclient.azurewebsites.net", headers: "*", 
    methods: "*", SupportsCredentials = true)]

如果此屬性為 true,HTTP 回應將包含 Access-Control-Allow-Credentials 標頭。 此標頭會告訴瀏覽器伺服器允許跨原始來源要求的認證。

如果瀏覽器傳送認證,但回應不包含有效的 Access-Control-Allow-Credentials 標頭,瀏覽器將不會向應用程式公開回應,而 AJAX 要求會失敗。

請小心將 SupportsCredentials 設定為 true,因為它表示另一個網域上的網站可以代表使用者將登入使用者的認證傳送至您的 Web API,而不需要使用者知道。 CORS 規格也指出如果SupportsCredentials為 true,將來源設定為 「*」 無效。

自訂 CORS 原則提供者

[EnableCors]屬性會實作ICorsPolicyProvider介面。 您可以建立衍生自 Attribute 的類別並實作 ICorsPolicyProvider,以提供您自己的實作。

[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false)]
public class MyCorsPolicyAttribute : Attribute, ICorsPolicyProvider 
{
    private CorsPolicy _policy;

    public MyCorsPolicyAttribute()
    {
        // Create a CORS policy.
        _policy = new CorsPolicy
        {
            AllowAnyMethod = true,
            AllowAnyHeader = true
        };

        // Add allowed origins.
        _policy.Origins.Add("http://myclient.azurewebsites.net");
        _policy.Origins.Add("http://www.contoso.com");
    }

    public Task<CorsPolicy> GetCorsPolicyAsync(HttpRequestMessage request)
    {
        return Task.FromResult(_policy);
    }
}

現在,您可以將屬性套用到您要放置 [EnableCors]的任何位置。

[MyCorsPolicy]
public class TestController : ApiController
{
    .. //

例如,自訂 CORS 原則提供者可以從組態檔讀取設定。

除了使用屬性,您也可以註冊建立 ICorsPolicyProviderFactory 物件的 ICorsPolicyProviderFactory 物件。

public class CorsPolicyFactory : ICorsPolicyProviderFactory
{
    ICorsPolicyProvider _provider = new MyCorsPolicyProvider();

    public ICorsPolicyProvider GetCorsPolicyProvider(HttpRequestMessage request)
    {
        return _provider;
    }
}

若要設定 ICorsPolicyProviderFactory,請在啟動時呼叫 SetCorsPolicyProviderFactory 擴充方法,如下所示:

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        config.SetCorsPolicyProviderFactory(new CorsPolicyFactory());
        config.EnableCors();

        // ...
    }
}

瀏覽器支援

Web API CORS 套件是伺服器端技術。 使用者的瀏覽器也需要支援 CORS。 幸運的是,所有主要瀏覽器的目前版本都包含 CORS 的支援