[.NET] 讓WebAPI可以跨網域執行,並動態指定允許呼叫的來源網域

一般來說,WebAPI只提供給自己網站使用的話是不用作任何調整的
但是WebAPI要開放給其他網站進行呼叫與使用,就必須進行跨網域的設定

網路上有很多關於跨網域的呼叫與使用,像是設定Access-Control-Allow-Origin,或是將client端的呼叫方式更改為JSONP等等的
若是使用的是.NET WebAPI的開發方式,很快速就可以完成跨網域存取的WebAPI,甚至連Client端呼叫的程式碼都不用去修改

要讓.NET的WebAPI啟用跨網域存取很簡單,照著下面的步驟就可以完成了

1.在WebAPI的專案中先增加一個[CorsController]的控制器

2.在專案中加入[Microsoft.AspNet.WebApi.Cors]的Nuget套件

3.安裝完成後,打開專案中的[App_Start\WebApiConfig.cs]檔案,並在裡面加上一行"config.EnableCors();"

4.最後,在剛剛增加的CorsController控制器中,加上下面的程式碼,就可以讓控制器中的Action提供指定來源網域的請求

[EnableCors(origins: "http://mywebclient.azurewebsites.net", headers: "*", methods: "*")]

其中,在EnableCors的Attribute中,origins代表允許存取的來源網域,headers代表允許的標頭,而method則是代表允許使用的方法,如Get或是Post等等,這幾個屬性的設定,若是更改為*,就代表允許所有的來源以及所有請求方式

要完成允許跨網域的WebAPI,這樣就可以達到我們要的需求了,不過如果是要更進一步,動態設定允許的來源網域該如何處理?

要作到動態網域存取,可以有下面兩種方式,不過這兩種方式的實作前提,都是在已經完成了[Microsoft.AspNet.WebApi.Cors]的Nuget套件安裝以及上述步驟的設定下進行實作的

實作一、透過[ICorsPolicyProvider]完成動態設定
透過ICorsPolicyProvider的方式,作到動態設定方式,必須在專案中增加一個AOP的物件,首先先在專案中加入一個[CorsHandle.cs]的類別庫並將下面的程式碼放入至這個類別庫之中

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

public class CorsHandle : Attribute, ICorsPolicyProvider
{
    private CorsPolicy objProlicy;

    public CorsHandle()
    {
        // 建立一個跨網域存取的原則物件
        objProlicy = new CorsPolicy
        {
            AllowAnyMethod = true,
            AllowAnyHeader = true
        };

        // 在這裡透過資料庫或是設定的方式,可動態加入允許存取的來源網域清單
        objProlicy.Origins.Add("http://myclient.azurewebsites.net");
        objProlicy.Origins.Add("http://www.facebook.com");
    }

    public Task<CorsPolicy> GetCorsPolicyAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        return Task.FromResult(objProlicy);
    }
}

在這個類別庫中,主要是繼承了Attribute以及ICorsPolicyProvider的介面,並實作在WebAPI執行時的跨網域驗證處裡,當然,在程式碼標註的位置中,可以動態的加入允許存取的網域名稱清單

接著回到CorsController的控制器中,將原本[EnableCors]的Attribute,更改成[CorsHandle],就可以在這個Action中套用網域的驗證動作了

實作二、透過自訂ActionFilter的AOP物件實作網域控制的驗證
在這個方式中,就比較偏向不採用ICorsPolicyProvider的方式來處理,而是在自訂的ActionFilter物件中進行過濾與驗證,同樣的,我們在專案中新增一個CorsOnActionHandle.cs的類別庫

將下面的程式碼,加入至類別庫中

using System.Net;
using System.Web.Http.Controllers;
using System.Web.Http.Filters;
public class CorsOnActionHandle : ActionFilterAttribute
{
    /// <summary>
    /// 進行專案使用時的授權驗證
    /// </summary>
    /// <param name="actionContext"></param>
    public override void OnActionExecuting(HttpActionContext actionContext)
    {
        // 設定允許的網域清單
        List<string> strAllowDomain = new List<string>()
        {
            "http://myclient.azurewebsites.net",
            "http://www.facebook.com"
        };

        // 取出來自呼叫端的網域
        string strOrigin = actionContext.Request.Headers.GetValues("Origin").FirstOrDefault();

        // 確認呼叫端的網域是否存在於允許的清單中
        bool blCheckDomain = strAllowDomain.Contains(strOrigin);

        // 如果不存在允許的網域清單,就回傳自訂的錯誤訊息
        if (!blCheckDomain)
        {
            UnauthorizedObject result = new UnauthorizedObject()
            {
                code = "401",
                message = "domain is not allow"
            };
            actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized, result);
        }
    }

    public class UnauthorizedObject
    {
        public string code { get; set; }
        public string message { get; set; }
    }
}

在上面的程式中可以很清楚的看得出來,透過了一般的ActionFilterAttribute在OnActionExecuting的覆寫處理上,當WebAPI一被呼叫時就把呼叫端的來源網域取出,並與已經設定好的網域清單作比對,若是來源網域不在允許的清單中,就透過CreateResponse的方法回傳指定的HttpStatusCode以及Response的內容。這樣的作法雖然較為繁瑣,但是可以提供的訊息內容以及使用的靈活性也比較高一些

最後,在要使用這個ActionFilter的Action上,加入相對應的Attribute在這裡別忘了把[EnableCors]的Attribute加上去,因為不是實作ICorsPolicyProvider,所以還是必須讓這個Action允許所有網域的呼叫,再透過ActionFilter去進行來源網域的過濾

參考資料:
Enabling Cross-Origin Requests in ASP.NET Web API 2

範例程式碼下載:
https://github.com/madukapai/maduka-WebAPI