跳到主要內容

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

電子商務之十大勝算與十大敗筆

文章轉載自網路 Success and Failure of e-commerce E-commerce has evolved since the late 1990s. Companies and individuals have been using e-commerce to do business; furthermore, it allows people to do shopping online. Many have succeeded, and many have failed as well. Let us now look at the reasons for the success and failure. 10 reasons for successful e-commerce   EC 十大勝算 1)Target the Un-Targeted 找到競爭者沒看到的客戶需求。 Know your customers thoroughly Target needs that are unseen by your competitors. 2)Strong Business Planning  強而有力的業務計畫:掌握收入來源與策略細節。搞定想達到的目標。一步一步前進。 Know your source of revenue, break-even and the strategic details Decide on the future goals that you want to achieve 3)Business Friendly Ecommerce Software  有簡單好用的軟體。 Choose software that is easy to manage and allows you to focus on core business activities 4) Create a Web Store with a Difference  品牌的建立非常重要。創造有吸引力的區塊名,建立不一樣的網路商店。 Branding is important for the success of your online business Attractive domain name can play an important r...