[WebAPI] 權限管理:透過AuthorizationFilterAttribute來客製化權限管理

  • 13021
  • 0
  • 2014-03-21

[WebAPI] 權限管理:透過AuthorizationFilterAttribute來客製化權限管理

 

在上一篇的文章介紹裡已經提到如何使用WebAPI內建的權限管理機制來檢查使用者權限,但當我們不使用角色或是使用者名稱,而改成用其他資訊例如Header裡的夾帶的「API Key」鍵值時該如何客製化符合自己需求的權限檢查邏輯呢?這時候我們可以透過繼承「AuthorizationFilterAttribute」標籤來處理。

 

開發一個客製化的authorization filter我們可以透過繼承以下兩個類別或是實作一個介面來完成:

  1. AuthorizeAttribute: 繼承此類別並透過使用者身分與角色來實作權限管理
  2. AuthorizationFilterAttribute: 繼承此類別並透過自訂資訊來實作權限管理
  3. IAuthorizationFilter: 實作此介面來執行非同步的權限檢查邏輯,例如當你的權限邏輯需要使用非同步來檢查

上述提到的類別與介面可透過以下的階層圖來瞭解其關係:

image

 

 

實作方式分成四個部分:

  1. CustomMessageHandler.cs
  2. CustomAuthorizationFilter.cs
  3. 在WebApiConfig.cs裡註冊CustomMessageHandler
  4. 在ValuesController的Action方法上掛載「CustomAuthorizationFilter」標籤

 

Step 1. CustomMessageHandler.cs

  1. 目的:我們需要一個自訂的MessageHandler幫我們把產生一個IPrincipal型別的物件並且塞到Thread.CurrentPrincipal的屬性裡。
  2. 流程:先檢查Header裡的「APIKey」欄位是否存在,如存在且鍵值等於「TestApiKey」時,產生一個使用者名稱為「Kevin」的GerericPrincipal物件並塞到Thread.CurrentPrincipal裡面。
using System;
using System.Linq;
using System.Net.Http;
using System.Security.Principal;
using System.Threading;
using System.Threading.Tasks;
using System.Web;

namespace MyWebAPI.MessageHandlers
{
    public class CustomMessageHandler : DelegatingHandler
    {
        /// <summary>
        /// Header名稱預設為「APIKey」
        /// </summary>
        private const string _header = "APIKey";

        protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            //// 檢查request裡是否有「APIKey」這個Header
            var apiKey = Enumerable.Empty<string>();
            bool isHeaderExist = request.Headers.TryGetValues(_header, out apiKey);

            //// 如果有「APIKey」這個Header且APIKey = "TestApiKey"
            if (isHeaderExist && string.Compare(apiKey.FirstOrDefault(), "TestApiKey", true) == 0)
            {
                this.SetPrincipal();
            }

            return base.SendAsync(request, cancellationToken);
        }

        /// <summary>
        /// 設定IPrincipal
        /// </summary>
        private void SetPrincipal()
        {
            //// 設定使用者識別 => 就是使用者名稱啦
            //// GenericIdentity.IsAuthenticated 預設為true
            GenericIdentity identity = new GenericIdentity("Kevin");

            //// 設定使用者所屬的群組
            String[] mMyStringArray = { "RD" };

            //// 將使用者的識別與其所屬群組設定到GenericPrincipal類別上
            GenericPrincipal principal = new GenericPrincipal(identity, mMyStringArray);

            Thread.CurrentPrincipal = principal;

            if (HttpContext.Current != null)
            {
                HttpContext.Current.User = principal;
            }
        }
    }
}

 

 

Step 2. CustomAuthorizationFilter.cs

  1. 目的:繼承AuthorizationFilterAttribute類別,並透過覆寫OnAuthorization的方法來實作自訂的權限檢查邏輯。
  2. 流程:將IPrincipal物件取出來,透過IAuthorizationService物件讓context端自己決定要如何使用IPrincipal裡的資訊來實現權限驗證。
using System.Net;
using System.Net.Http;
using System.Security.Principal;
using System.Threading;
using System.Web.Http.Filters;

namespace MyWebAPI.Filters
{
    public class CustomAuthorizationFilter : AuthorizationFilterAttribute
    {
        /// <summary>
        /// The authorization service
        /// </summary>
        private IAuthorizationService authorizationService = new CustomAuthorizationService();

        /// <summary>
        /// The authenticated username
        /// </summary>
        private const string authenticatedUsername = "Kevin";

        /// <summary>
        /// 在處理序要求授權時呼叫。
        /// </summary>
        /// <param name="actionContext">動作內容,該內容封裝 <see cref="T:System.Web.Http.Filters.AuthorizationFilterAttribute" /> 的使用資訊。</param>
        public override void OnAuthorization(System.Web.Http.Controllers.HttpActionContext actionContext)
        {
            bool isAuthorizated = false;

            //// 從Thread取出IPrincipal
            IPrincipal principal = Thread.CurrentPrincipal;
            isAuthorizated = authorizationService.IsAuthorizated(principal);

            if (!isAuthorizated)
            {
                //// CreateResponse是System.Net.Http命名空間的擴充方法
                actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized);
            }
        }
    }

    /// <summary>
    /// IAuthorizationService
    /// </summary>
    public interface IAuthorizationService
    {
        /// <summary>
        /// 檢查該使用者的名稱是否有權限
        /// </summary>
        bool IsAuthorizated(IPrincipal principal);
    }

    /// <summary>
    /// CustomAuthorizationService
    /// </summary>
    public class CustomAuthorizationService : IAuthorizationService
    {
        public bool IsAuthorizated(IPrincipal principal)
        {
            //// 這邊context端需要實作自訂的權限檢查邏輯,範例裡透過檢查Identity.Name是否等於「Kevin」來判斷
            bool isIdentityAuthenticated = principal.Identity.IsAuthenticated;
            bool isUsernameCorrect = principal.Identity.Name == "Kevin";

            return isIdentityAuthenticated && isUsernameCorrect;
        }
    }
}

 

Step 3. 在WebApiConfig.cs裡註冊CustomMessageHandler

using System.Web.Http;
using MyWebAPI.MessageHandlers;

namespace MyWebAPI
{
    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );

            //// 註冊CustomMessageHandler
            config.MessageHandlers.Add(new CustomMessageHandler());

            // 取消註解以下程式碼行以啟用透過 IQueryable 或 IQueryable<T> 傳回類型的動作查詢支援。
            // 為了避免處理未預期或惡意佇列,請使用 QueryableAttribute 中的驗證設定來驗證傳入的查詢。
            // 如需詳細資訊,請造訪 http://go.microsoft.com/fwlink/?LinkId=279712。
            //config.EnableQuerySupport();

            // 若要停用您應用程式中的追蹤,請將下列程式碼行標記為註解或加以移除
            // 如需詳細資訊,請參閱: http://www.asp.net/web-api
            config.EnableSystemDiagnosticsTracing();
        }
    }
}

 

 

Step 4. 在ValuesController的Action方法上掛載「CustomAuthorizationFilter」標籤

using System.Collections.Generic;
using System.Web.Http;
using MyWebAPI.Filters;

namespace MyWebAPI.Controllers
{

    public class ValuesController : ApiController
    {
        [CustomAuthorizationFilter]
        public IEnumerable<string> Get()
        {
            return new string[] { "value1", "value2" };
        }
    }
}

 

注意事項:

  1. 當Http Request進入WebAPI的管線時,Thread.CurrentPrincipal.Identity物件就已存在,且Thread.CurrentPrincipal.Identity.Name為空字串。
  2. 當我們在覆寫OnAuthorization方法的時候,透過ActionContext.Request來回傳HttpResponseMessage時要記得加上「System.Net.Http」的命名空間,這麼一來我們才可以使用CreateResponse的擴充方法。