[WebAPI] 權限管理:檢查使用者權限

  • 15378
  • 0
  • 2014-03-21

摘要:[WebAPI] 權限管理:檢查使用者權限

 

在建立好WebAPI專案後,當我們想透過WebAPI內建的權限管理機制來檢查使用者權限,這時候我們可以透過內建的AuthorizeAttribute「權限標籤」來使用這個服務。

 

檢查權限的時間點:

當HttpRequest進入WebAPI時會先經過HttpMessageHandler的處理,Request在進入Controller之前會先透過Authorization Filter過濾器檢查該Request是否擁有權限使用Controller提供的服務。

 

Authorization Filter「權限過濾器」的使用方式:

套用Authorization Filter過濾器可以放在三種不同的層級:

  1. Global層級:針對WebAPI全站處理的Request都要檢查權限時,我們可以把AuthorizeAttribute「權限標籤」註冊在WebAPI站台上的設定檔。
    public static void Register(HttpConfiguration config)
    {
        config.Filters.Add(new AuthorizeAttribute());
    }
  2. Controller層級:只針對ValuesController提供的服務檢查權限。
    [Authorize]
    public class ValuesController : ApiController
    {
        public IEnumerable Get()
        {
            return new string[] { "value1", "value2" };
        }
    
        public string Get(int id)
        {
            return "value";
        }
    }

     

  3. Action層級:只針對ValuesController裡的「Get()」Action來檢查權限。
    public class ValuesController : ApiController
    {
        [Authorize]
        public IEnumerable Get()
        {
            return new string[] { "value1", "value2" };
        }
    
        public string Get(int id)
        {
            return "value";
        }
    }

     

檢查權限的流程:

在.NET裡面,當Request經過HttpMessageHandler時被驗證過身份後,這時系統會產生一個IPrincipal的物件附加在Thread裡用來存放身分識別的資訊,接著在Authorization Filter過濾器裡,我們就可以把IPrincipal物件從Thread裡面取出來,透過IPrincipal物件上夾帶的身分識別資訊,例如使用者名稱或是ApiKey等等來檢查這個Request是否有權限執行某個Action。

 

 

把IPrincipal物件附著在Thread裡面跟取出的方式:

private void SetPrincipal(IPrincipal principal)
{
    Thread.CurrentPrincipal = principal;
}

private IPrincipal GetPrincipal()
{
    var principal = Thread.CurrentPrincipal;
    return principal;
}

 

如何產生IPrincipal物件:

我們可以參考這篇文章的教學來產生一個IPrincipal的物件,類別圖整理如下供大家參考。

 

 

撰寫程式:

Step1: 在AuthorizationDemo的WebAPI專案底下產生一個AuthenticationMessageHandler的類別。

我們需要先透過這個類別產生IPrincipal物件並附加到Thread裡。

 

 

Step2: 在AuthenticationMessageHandler類別裡產生IPrincipal的物件,並將IPrincipal附加到Thread.CurrentPrincipal裡。

using System.Net.Http;
using System.Security.Principal;
using System.Threading;
using System.Threading.Tasks;

namespace AuthorizationDemo.MessageHandlers
{
    public class AuthenticationMessageHandler : DelegatingHandler
    {
        /// <summary>
        /// Sends the async.
        /// </summary>
        /// <param name="request">The request.</param>
        /// <param name="cancellationToken">The cancellation token.</param>
        /// <returns></returns>
        protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            //// 產生IPrincipal
            var principal = this.CreateIPrincipal();

            //// 設定IPrincipal
            this.SetPrincipal(principal);

            //// 呼叫base.SendAsync將request傳給inner handler去處理,inner handler非同步處理完會回傳Task<T>的物件
             var result = base.SendAsync(request, cancellationToken);
            return result;
        }

        /// <summary>
        /// 產生IPrincipal的物件
        /// </summary>
        /// <returns>IPrincipal</returns>
        private IPrincipal CreateIPrincipal()
        {
            //// 建立一個使用者名稱為"Kevin"的GenericIdentity物件,同時設定該使用者的角色為"RD"與"QA"
            GenericIdentity identity = new GenericIdentity("Kevin");
            string[] userRoles = { "RD", "QA" };
            GenericPrincipal principal = new GenericPrincipal(identity, userRoles);
            return principal;
        }

        /// <summary>
        /// 將IPrincipal附加到Thread.CurrentPrincipal裡
        /// </summary>
        /// <param name="principal">The principal.</param>
        private void SetPrincipal(IPrincipal principal)
        {
            Thread.CurrentPrincipal = principal;
        }
    }
}

Step3: 將AuthenticationMessageHandler註冊到WebApiConfig.cs。

using System.Web.Http;
using AuthorizationDemo.MessageHandlers;

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

            //// 在這邊註冊剛剛寫好的AuthenticationMessageHandler
            config.MessageHandlers.Add(new AuthenticationMessageHandler());

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

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

Step4: 把Authorize標籤掛在ValuesController裡的Get()方法上。

在這邊請注意我們只允許使用者名稱為「Alice或Bob」的Request才能使用此服務,而在剛剛AuthenticationMessageHandler類別裡我們設定的使用者名稱為「Kevin」。

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

namespace AuthorizationDemo.Controllers
{
    public class ValuesController : ApiController
    {
        [Authorize(Users = "Alice,Bob")]
        public IEnumerable<string> Get()
        {
            return new string[] { "value1", "value2" };
        }

        public string Get(int id)
        {
            return "value";
        }
    }
} 

 

Step5: 透過TestClient打ValuesController的服務我們會看到WebAPI回傳Http 401的訊息。

 

 

延伸場景1:當使用者的角色為「PM」和「RM」時,允許存取服務

[Authorize(Roles = "PM,RM")]
public IEnumerable<string> Get()
{
    return new string[] { "value1", "value2" };
}

 

延伸場景2:當使用者的角色為「PM」和「RM」時且使用者名稱為「Alice」或「Bob」時,允許存取服務

[Authorize(Roles = "PM,RM", Users = "Alice,Bob")]
public IEnumerable<string> Get()
{
    return new string[] { "value1", "value2" };
}

 

 

思考問題:

  1. 當檢查權限的邏輯從「角色」或「使用者名稱」替換成「API Key」時我們該怎麼處理?
  2. 如何客製化符合自己需求的權限檢查邏輯呢?

 

參考:

  1. Authentication and Authorization in ASP.NET Web API
  2. How to: Create GenericPrincipal and GenericIdentity Objects
  3. GenericIdentity Class
  4. Http Basic Access Authentication
  5. A WebAPI Basic Authentication Authorization Filter