許多 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();
}
}
眼尖的你,可能會發現AdminOnlyAttribute、SupervisorOnlyAttribute與PMOnlyAttribute沒啥不同,都是只修改了建構式而已,為什麼要讓AdminOnlyAttribute、SupervisorOnlyAttribute去繼承PMOnlyAttribute呢?
原因是有關AuthorizeAttribute還沒講完,有兩個重要的方法,必須要瞭解。
那就是OnAuthorization與HandleUnauthorizedRequest
HandleUnauthorizedRequest | 處理授權失敗的HTTP 要求。 |
OnAuthorization | 在處理序要求授權時呼叫。 |
通常,我們繼承類別,自行撰寫之目的,就是要對系統行為多一些控制權,例如,使用者的角色沒有權限操作某些功能,或已經登出系統後,再試圖操作時,系統應該出現提示,將使用者導向說明頁面,或是登入頁面。
而這種設計,在AdminOnlyAttribute、SupervisorOnlyAttribute也同樣會用到,既然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);
}
}
}
}
留言
就您的解法最簡單、正確,
感恩
很高興有人回應!