跳到主要內容

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的功能,編譯器在編譯整個工程的時候,它 只會編譯那些經過修改的文件,而不會去編譯那些從上次編譯過,到現在沒有被修改過 的文件。那麼為什麼還要預編譯頭文件呢?答案在這裡,我們知道編譯器是以文件為單 位編譯的,一個文件經過修改後,

開關CodeIgniter之PHP錯誤訊息

參考CI之 Error Handle說明文件 在index.php的第一行程式碼 error_reporting(E_ALL); 這樣即預設為全部回報 若要關閉錯誤回報,則輸入參數0即可 error_reporting(0); 下面為錯誤回報等級 value constant 1 E_ERROR 2 E_WARNING 4 E_PARSE 8 E_NOTICE 16 E_CORE_ERROR 32 E_CORE_WARNING 64 E_COMPILE_ERROR 128 E_COMPILE_WARNING 256 E_USER_ERROR 512 E_USER_WARNING 1024 E_USER_NOTICE 6143 E_ALL 2048 E_STRICT 4096 E_RECOVERABLE_ERROR PHP原文參考處