摘要:[WebAPI] 權限管理:檢查使用者權限
在建立好WebAPI專案後,當我們想透過WebAPI內建的權限管理機制來檢查使用者權限,這時候我們可以透過內建的AuthorizeAttribute「權限標籤」來使用這個服務。
檢查權限的時間點:
當HttpRequest進入WebAPI時會先經過HttpMessageHandler的處理,Request在進入Controller之前會先透過Authorization Filter過濾器檢查該Request是否擁有權限使用Controller提供的服務。
Authorization Filter「權限過濾器」的使用方式:
套用Authorization Filter過濾器可以放在三種不同的層級:
-
Global層級:針對WebAPI全站處理的Request都要檢查權限時,我們可以把AuthorizeAttribute「權限標籤」註冊在WebAPI站台上的設定檔。
public static void Register(HttpConfiguration config) { config.Filters.Add(new AuthorizeAttribute()); }
-
Controller層級:只針對ValuesController提供的服務檢查權限。
[Authorize] public class ValuesController : ApiController { public IEnumerable
Get() { return new string[] { "value1", "value2" }; } public string Get(int id) { return "value"; } } -
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" };
}
思考問題:
- 當檢查權限的邏輯從「角色」或「使用者名稱」替換成「API Key」時我們該怎麼處理?
- 如何客製化符合自己需求的權限檢查邏輯呢?
參考: