跳到主要內容

ASP.Net MVC2 AuthorizeAttribute 的自訂用法

許多 Web 應用程式都需要使用者登入,再授權讓使用者存取受限制的內容。在某些應用程式中,對於登入使用者也可能會限制可檢視的內容或可編輯的欄位。

若要限制對 ASP.NET MVC 檢視的存取,您可以限制存取呈現檢視的動作方法。為了達成此目的,MVC 架構提供了 AuthorizeAttribute 類別。

基本上,MSDN只有這篇AuthorizeAttribute類別

http://msdn.microsoft.com/zh-tw/library/system.web.mvc.authorizeattribute.aspx

還有這篇:「ASP.NET MVC 應用程式中的動作篩選」有提到

http://msdn.microsoft.com/zh-tw/library/dd410209.aspx

MSDN範例如下,雖然使用方式很簡單,但是初學者應該會有很多疑惑;例如:

1. 掛上[Authorize]屬性,就可以限制使用者了嗎?

2. [Authorize(Users = "Betty, Johnny")]可以用來限制特定使用,那使用者身分如何取得?

3. [Authorize(Roles = "Admin, Super User")]可以用來限制角色存取,那如何給定使用者的角色身分?

4. 為何只要給字串(Users = "Betty, Johnny")(Roles = "Admin, Super User")就有效了?

如果上述問題,對你來說,都可以回答,那基本上,你已經是很嫻熟ASP.Net MVC AuthorizeAttribute的機制。

public class HomeController : Controller
{
public ActionResult Index()
{
ViewData["Message"] = "Welcome to ASP.NET MVC!";
return View();
}

public ActionResult About()
{
return View();
}

[Authorize]
public ActionResult AuthenticatedUsers()
{
return View();
}

[Authorize(Roles = "Admin, Super User")]
public ActionResult AdministratorsOnly()
{
return View();
}

[Authorize(Users = "Betty, Johnny")]
public ActionResult SpecificUserOnly()
{
return View();
}
}

但是這種寫法,遇到要增加角色或使用者的時候,就要去每個Controller,甚至每個Action去修改這個Attribute,相當不便。而且萬一打錯字,就無效了,所以想能不能使用列舉或其它方式來避免這種錯誤?

於是,我們可以利用繼承AuthorizeAttribute的方式,自己做一個,例如:

首先,定義一個使用者角色的列舉:

public enum EnumPMRole
{
PMAdmin = 0,
PMSupervisor = 1,
PMUser = 2
}

public class PMOnlyAttribute : AuthorizeAttribute
{
public PMOnlyAttribute()
{
var authorizedRoles = new[] {EnumPMRole.PMAdmin.ToString(), EnumPMRole.PMSupervisor.ToString(), EnumPMRole.PMUser.ToString() };
Roles = string.Join(",", authorizedRoles);
}
}

這種做法是在建構式當中,將所要允許的角色,指定到AuthorizeAttribute.Roles的屬性中,利用AuthorizeAttribute本身對於角色的處理功能,就可以輕易達到限制特定角色存取特定功能之目的了。

如果想要限制另外的角色群,例如,限制只有最高管理員(PMAdmin)才能使用的功能,那要如何修改這支Attribute

一般來講,權限設計的架構,是呈金字塔型的,所以最低權限的功能,就掛上允許最多角色使用的AuthorizeAttribute;越高權限的功能,則掛上較少的角色。

同樣地,可以利用建構式,初始化的時候,指定允許使用的角色,我們可以再次繼承AuthorizeAttribute類別,另外設計一個過濾器來用,不過這次,我們可以直接繼承PMOnlyAttribute就可以了,原因後面再說明。

//限制最高管理員才能使用
public class AdminOnlyAttribute : PMOnlyAttribute
{
public AdminOnlyAttribute()
{
var authorizedRoles = new[] { EnumPMRole.PMAdmin.ToString() };
Roles = string.Join(",", authorizedRoles);
}
}
//限制最高管理員、主管級別才能使用
public class SupervisorOnlyAttribute : PMOnlyAttribute
{
public SupervisorOnlyAttribute()
{
var authorizedRoles = new[] { EnumPMRole.PMAdmin.ToString() };
Roles = string.Join(",", authorizedRoles);
}
}

用起來,就像下面這樣

public class AdminController : Controller
{
//使用者列表:僅最高管理員可用
[AdminOnly]
public ActionResult PMList()
{
return View();
}

//賣場統計資訊,主管級別以上可用
[SupervisorOnly]
public ActionResult MarketCount()
{
return View();
}
}

眼尖的你,可能會發現AdminOnlyAttributeSupervisorOnlyAttributePMOnlyAttribute沒啥不同,都是只修改了建構式而已,為什麼要讓AdminOnlyAttributeSupervisorOnlyAttribute去繼承PMOnlyAttribute呢?

原因是有關AuthorizeAttribute還沒講完,有兩個重要的方法,必須要瞭解。

那就是OnAuthorizationHandleUnauthorizedRequest

HandleUnauthorizedRequest

處理授權失敗的HTTP 要求。

OnAuthorization

在處理序要求授權時呼叫。

通常,我們繼承類別,自行撰寫之目的,就是要對系統行為多一些控制權,例如,使用者的角色沒有權限操作某些功能,或已經登出系統後,再試圖操作時,系統應該出現提示,將使用者導向說明頁面,或是登入頁面。

而這種設計,在AdminOnlyAttributeSupervisorOnlyAttribute也同樣會用到,既然PMOnlyAttribute是所有角色都會用到的,那就把這段程式碼寫在這,讓之後衍生的類別,都可以享用到。

public override void OnAuthorization(AuthorizationContext filterContext)
{
base.OnAuthorization(filterContext);

var thisUser = filterContext.RequestContext.HttpContext.User;
//確認已通過驗證
if (HttpContext.Current.User.Identity.IsAuthenticated)
{
//用父類別的驗證,判斷是否在角色內
if (!AuthorizeCore(filterContext.HttpContext))
{
filterContext.Result = new RedirectToRouteResult("MyArea_default",
new RouteValueDictionary
{
{ "controller", "Home" },
{ "action", "NotAllow" },
{ "id", UrlParameter.Optional }
});
}
}
else
{
// 未登入,轉至登入頁面
string rtURL = "";
rtURL = filterContext.HttpContext.Request.RawUrl;
filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary
{
{ "controller", "Home" },
{ "action", "Login" },
{ "ReturnUrl", rtURL }
});
}
}

///
/// 處理未授權的要求,導到登入頁面
///

///
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
filterContext.Result = new RedirectToRouteResult("MyArea_default",
new RouteValueDictionary
{
{ "controller", "Home" },
{ "action", "Login" },
{ "id", UrlParameter.Optional }
});
return;
}


最後,是最基本,也是最重要的,就是在Global.asax中,必須設定好授權委派的程式區段,讓授權的使用者,在過濾器作用之前,能得到角色,繼而讓過濾器檢查。

範例如下:

public MvcApplication()
{
AuthorizeRequest += new EventHandler(MvcApplication_AuthorizeRequest);
}

void MvcApplication_AuthorizeRequest(object sender, EventArgs e)
{
if (HttpContext.Current.User != null)
{
if (HttpContext.Current.User.Identity.IsAuthenticated)
{
///確認已通過驗證
if (HttpContext.Current.User.Identity is FormsIdentity)
{
FormsIdentity id = (FormsIdentity)HttpContext.Current.User.Identity;
//建立FormsAuthenticationTicket物件
FormsAuthenticationTicket ticket = id.Ticket;
//取得角色資料
string userdata = ticket.UserData;
var roles = userdata.Split(',');
//授予該使用者新的角色
HttpContext.Current.User = new System.Security.Principal.GenericPrincipal(id, roles);
}
}
}
}

留言

匿名表示…
爬了好多阿斗仔的文章,
就您的解法最簡單、正確,
感恩
超猛鴨表示…
這也是爬了很多阿斗仔的文章,再依照自己需要,整理出來的。
很高興有人回應!

這個網誌中的熱門文章

終於搞懂了,預編譯頭文件(precompiled header)

預編譯頭文件 今天在改一個很大的程序,慢慢看,慢慢改。突然發現一個.c文件,裡面什麼也沒有, 就幾個頭文件,我一看,我靠,這不是把簡單的問題搞複雜了嗎,隨手刪掉那個c文件。 結果不能編譯了,我靠: fatal error C1083: Cannot open precompiled header file: \'Debug/v13_3.pch\': No such file or directory 怎麼rebuild all都不行。 上網查了一下,才搞懂了: ----------------總結------ 如果工程很大,頭文件很多,而有幾個頭文件又是經常要用的,那麼 1。把這些頭文件全部寫到一個頭文件裡面去,比如寫到preh.h 2。寫一個preh.c,裡面只一句話:#include "preh.h" 3。對於preh.c,在project setting裡面設置creat precompiled headers,對於其他 .c文件,設置use precompiled header file // 哈哈 我試了一下,效果很明顯,不用precompiled header,編譯一次我可以去上個廁所,用 precompiled header,編譯的時候,我可以站起來伸個懶腰,活動活動就差不多啦 ---------轉載的文章---------- 預編譯頭的概念: 所謂的預編譯頭就是把一個工程中的那一部分代碼,預先編譯好放在一個文件裡(通常是 以.pch為擴展名的),這個文件就稱為預編譯頭文件這些預先編譯好的代碼可以是任何的 C/C++代碼--------甚至是inline的函數,但是必須是穩定的,在工程開發的過程中不會 被經常改變。如果這些代碼被修改,則需要重新編譯生成預編譯頭文件。注意生成預編 譯頭文件是很耗時間的。同時你得注意預編譯頭文件通常很大,通常有6-7M大。注意及 時清理那些沒有用的預編譯頭文件。 也許你會問:現在的編譯器都有Time stamp的功能,編譯器在編譯整個工程的時候,它 只會編譯那些經過修改的文件,而不會去編譯那些從上次編譯過,到現在沒有被修改過 的文件。那麼為什麼還要預編譯頭文件呢?答案在這裡,我們知道編譯器是以文件為單 位編譯的,一個文件經過修改後,...

當女友升級為妻子之後

某使用者詢問: 去年,我把 [女友1.0版] 升級成 [妻子1.0版] ,我注意到新的程式開始出現意料外的狀況,如小孩製造過程佔用了相當多的空間和資源,產品手冊當中並未提及這種現象。 此外,[妻子1.0版] 會把自己裝置到其他的應用程式當中,並且當其他系統起用的時候會自行啟動,監視其他系統所進行的所有活動。諸如 [撲克牌之夜1.3] 和 [猛灌啤酒2.5] 之類的應用軟體已經無法執行,所有相關系統都被摧毀。 我考慮回復到 [女友1.0版] ,但是 un-install 卻無效,你能夠幫我嗎? ------------------------------------------------------------ 技術支援部門的回答: xx先生您好,您的問題極為常見,且通常是因為您的基本誤解而產生的。 很多人把 [女友1.0版] 升級成為 [妻子1.0版] ,認為 [妻子1.0版] 純粹只是一種利用及娛樂性質的程式。其實 [妻子1.0版] 是一種運作系統,其創造者將其設計為一種能夠執行所有任務的軟體。這個軟體,你就再也無法從系統中un-install、delete或purge她。你不可能將 [妻子1.0版] 回復成 [女友1.0版] 的。 有些人曾企圖 install [女友2.0版] ,結果卻問題比原來的系統還多。請查看您手冊當中有關[警告-贍養費/撫養孩童] 的部分。我建議你還是留住 [妻子1.0版] ,處理目前所面臨的情況就好。 我本身也 install[妻子1.0版] ,我想順便建議你閱讀整個有關 [一般性保護失誤] (General Protection Faults, GPFs) 的部分。你可以預先假設所有可能發生的責任和失誤。發生當機時,最正確的動作就是按下 [道歉] 按鈕,然後再按下 [重新開機] 鈕。只要你能夠承擔所有 [一般性保護失誤] 的罪過,整個系統就可以正常運作。 [妻子1.0版] 是個很棒的程式,但是需要你經常去維護。 ------------------------------------------------------------ 軟體部門回答: 親愛的使用者,您好! 由於「妻子1.0版」無法un-install的問題,造成許多用戶的不便,本公司軟體部門同仁深表歉意! 但由於 「妻子1.0版」 的創始用意,乃在於提供一套能夠永久...