asp.net MVC之AuthorizeAttribute淺析


AuthorizeAttribute是asp.net MVC的幾大過濾器之一,俗稱認證和授權過濾器,也就是判斷登錄與否,授權與否。當為某一個Controller或Action附加該特性時,沒有登錄或授權的賬戶是不能訪問這些Controller或Action的。

在進入一個附加了Authorize特性的Controller或Action之前,首先執行的是AuthorizeAttribute類的OnAuthorization(AuthorizationContext filterContext)方法,接着OnAuthorization方法會去調用其他的諸如AuthorizeCore方法。

在將AuthorizeAttribute類反編譯之后得到如下代碼,一看就清晰明了:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Principal;
using System.Web.Mvc.Properties;
namespace System.Web.Mvc
{
    /// <summary>Specifies that access to a controller or action method is restricted to users who meet the authorization requirement.</summary>
    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
    public class AuthorizeAttribute : FilterAttribute, IAuthorizationFilter
    {
        private static readonly char[] _splitParameter = new char[]
        {
            ','
        };
        private readonly object _typeId = new object();
        private string _roles;
        private string[] _rolesSplit = new string[0];
        private string _users;
        private string[] _usersSplit = new string[0];
        /// <summary>Gets or sets the user roles that are authorized to access the controller or action method.</summary>
        /// <returns>The user roles that are authorized to access the controller or action method.</returns>
        public string Roles
        {
            get
            {
                return this._roles ?? string.Empty;
            }
            set
            {
                this._roles = value;
                this._rolesSplit = AuthorizeAttribute.SplitString(value);
            }
        }
        /// <summary>Gets the unique identifier for this attribute.</summary>
        /// <returns>The unique identifier for this attribute.</returns>
        public override object TypeId
        {
            get
            {
                return this._typeId;
            }
        }
        /// <summary>Gets or sets the users that are authorized to access the controller or action method.</summary>
        /// <returns>The users that are authorized to access the controller or action method.</returns>
        public string Users
        {
            get
            {
                return this._users ?? string.Empty;
            }
            set
            {
                this._users = value;
                this._usersSplit = AuthorizeAttribute.SplitString(value);
            }
        }
        /// <summary>When overridden, provides an entry point for custom authorization checks.</summary>
        /// <returns>true if the user is authorized; otherwise, false.</returns>
        /// <param name="httpContext">The HTTP context, which encapsulates all HTTP-specific information about an individual HTTP request.</param>
        /// <exception cref="T:System.ArgumentNullException">The <paramref name="httpContext" /> parameter is null.</exception>
        protected virtual bool AuthorizeCore(HttpContextBase httpContext)
        {
            if (httpContext == null)
            {
                throw new ArgumentNullException("httpContext");
            }
            IPrincipal user = httpContext.User;
            return user.Identity.IsAuthenticated && (this._usersSplit.Length <= 0 || this._usersSplit.Contains(user.Identity.Name, StringComparer.OrdinalIgnoreCase)) && (this._rolesSplit.Length <= 0 || this._rolesSplit.Any(new Func<string, bool>(user.IsInRole)));
        }
        private void CacheValidateHandler(HttpContext context, object data, ref HttpValidationStatus validationStatus)
        {
            validationStatus = this.OnCacheAuthorization(new HttpContextWrapper(context));
        }
        /// <summary>Called when a process requests authorization.</summary>
        /// <param name="filterContext">The filter context, which encapsulates information for using <see cref="T:System.Web.Mvc.AuthorizeAttribute" />.</param>
        /// <exception cref="T:System.ArgumentNullException">The <paramref name="filterContext" /> parameter is null.</exception>
        public virtual void OnAuthorization(AuthorizationContext filterContext)
        {
            if (filterContext == null)
            {
                throw new ArgumentNullException("filterContext");
            }
            if (OutputCacheAttribute.IsChildActionCacheActive(filterContext))
            {
                throw new InvalidOperationException(MvcResources.AuthorizeAttribute_CannotUseWithinChildActionCache);
            }
            bool flag = filterContext.ActionDescriptor.IsDefined(typeof(AllowAnonymousAttribute), true) || filterContext.ActionDescriptor.ControllerDescriptor.IsDefined(typeof(AllowAnonymousAttribute), true);
            if (flag)
            {
                return;
            }
            if (this.AuthorizeCore(filterContext.HttpContext))
            {
                HttpCachePolicyBase cache = filterContext.HttpContext.Response.Cache;
                cache.SetProxyMaxAge(new TimeSpan(0L));
                cache.AddValidationCallback(new HttpCacheValidateHandler(this.CacheValidateHandler), null);
                return;
            }
            this.HandleUnauthorizedRequest(filterContext);
        }
        /// <summary>Processes HTTP requests that fail authorization.</summary>
        /// <param name="filterContext">Encapsulates the information for using <see cref="T:System.Web.Mvc.AuthorizeAttribute" />. The <paramref name="filterContext" /> object contains the controller, HTTP context, request context, action result, and route data.</param>
        protected virtual void HandleUnauthorizedRequest(AuthorizationContext filterContext)
        {
            filterContext.Result = new HttpUnauthorizedResult();
        }
        /// <summary>Called when the caching module requests authorization.</summary>
        /// <returns>A reference to the validation status.</returns>
        /// <param name="httpContext">The HTTP context, which encapsulates all HTTP-specific information about an individual HTTP request.</param>
        /// <exception cref="T:System.ArgumentNullException">The <paramref name="httpContext" /> parameter is null.</exception>
        protected virtual HttpValidationStatus OnCacheAuthorization(HttpContextBase httpContext)
        {
            if (httpContext == null)
            {
                throw new ArgumentNullException("httpContext");
            }
            if (!this.AuthorizeCore(httpContext))
            {
                return HttpValidationStatus.IgnoreThisRequest;
            }
            return HttpValidationStatus.Valid;
        }
        internal static string[] SplitString(string original)
        {
            if (string.IsNullOrEmpty(original))
            {
                return new string[0];
            }
            IEnumerable<string> source = 
                from piece in original.Split(AuthorizeAttribute._splitParameter)
                let trimmed = piece.Trim()
                where !string.IsNullOrEmpty(trimmed)
                select trimmed;
            return source.ToArray<string>();
        }
    }
}

 

通過以上描述,我們可以在自己的MVC程序中使用AuthorizeAttribute類或者其派生的子類來達到用戶認證和授權的目的。

一、新建一個MVC項目

新建一個MVC項目,選擇不用身份驗證。

 

二、添加Model

為了達到演示目的,添加了一個簡單的StudentViewModel,用來顯示業務數據:

public class StudentViewModel {
        public int ID { get; set; }
        public string Name { get; set; }
        public string Tel { get; set; }
    }

添加一個UserViewModel,用來進行登錄和角色授權:

    public class UserViewModel {
        [DisplayName("用戶名")]
        public string UserName { get; set; }
        [DisplayName("密  碼")]
        public string Pwd { get; set; }       
        public Role Role { get; set; }
    }

    public enum Role {
        Admin=1,
        Normal=2,
        System=3
    }

OK,本篇講述就不去訪問數據庫了,直接用業務數據。然后我們再新建一個Data類,存放業務數據:

    public class Data {
        public static List<StudentViewModel> students = new List<StudentViewModel> {
            new StudentViewModel { ID=1, Name="張三", Tel="15808038502" },
            new StudentViewModel { ID=2, Name="李四", Tel="15708032302" },
            new StudentViewModel { ID=3, Name="王五", Tel="15562438502" },
            new StudentViewModel { ID=4, Name="趙六", Tel="15064534502" },
            new StudentViewModel { ID=5, Name="孫琦", Tel="15185465402" }
        };

        public static List<UserViewModel> users = new List<UserViewModel> {
            new UserViewModel { UserName="admin", Pwd="1", Role=Role.Admin },
            new UserViewModel { UserName="normal", Pwd="1", Role=Role.Normal},
            new UserViewModel { UserName="system", Pwd="1", Role=Role.System}
        };       
    }

 

三、創建Controller控制器和視圖

1、新建StudentController

Index視圖是顯示全部的Student數據,FindOne是顯示一條Student數據。:

    public class StudentController : Controller {
        
        public ActionResult Index() {
            return View(Data.Data.students);
        }
        public ActionResult FindOne(int? id) {
            return View("Index", Data.Data.students.Where(s => s.ID == id));
        }
    }

現在沒有對Student控制器做任何的身份和授權訪問限制。接下來我們創建一個登陸用的控制器。

2、新建AccountController

    public class AccountController : Controller {
        //用於獲取登錄頁面
        [HttpGet]
        public ActionResult Login() {
            return View();
        }

        //用於登錄驗證
        [HttpPost]
        public ActionResult Login(LoginViewModel login) {
            if (ModelState.IsValid) {
                UserViewModel clogin = Data.Data.users.Where(u => u.UserName == login.UserName && u.Pwd == login.Pwd).FirstOrDefault();
                if (clogin != null) {
                    //FormsAuthentication.SetAuthCookie有兩個參數
                    //第一個參數:設置當前登錄用戶的標識,可以自定義
                    //第二個參數:
                    //true:設置永久的。可以在web.config中設置過期時間,喏,下面的timeout就是過期時間,以分鍾為單位。如下所示:
                    //     < system.web >
                    //         < authentication mode = "Forms" >
                    //           < forms loginUrl = "~/Account/Login" timeout = "2" />
                    //        </ authentication >
                    //     </ system.web >
                    //false:設置當前會話時間為有效時間,瀏覽器或頁面已關閉就需要重新登錄。
                    FormsAuthentication.SetAuthCookie(clogin.UserName, false);
                    return RedirectToAction("FindOne", "Student", new { id = 1 });
                }
            }
            return View();
        }

        //用於注銷登錄
        [Authorize]
        [NoCache]
        public ActionResult LoginOut() {
            FormsAuthentication.SignOut();
            return RedirectToAction("Login", "Account");
        }

        //用於顯示無權限消息
        public ActionResult NoPemission() {
            return View();
        }
    }

登陸頁面如下:

@model MVCDemo2.ViewModels.UserViewModel
@{
    Layout = null;
}

<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Login</title>
    <link href="~/Content/w3.css" rel="stylesheet" />
    <style>
        html, body {
            margin: 0;
            padding: 0;
        }

        .main {
            width: 400px;
            height: 300px;
            position: absolute;
            left: 0;
            top: 0;
            right: 0;
            bottom: 0;
            margin: auto;
        }
    </style>
</head>
<body>

    <div class="main">
        <h3 class="w3-teal w3-center w3-margin-0" style="width:100%;">權限管理系統</h3>
        @using (Html.BeginForm("Login", "Account", FormMethod.Post, new { @class = "w3-container w3-card-4 w3-light-grey", style = "width:100%;" })) {           
                <table class="w3-table">
                    <tr>
                        <td style="width:20%; vertical-align:middle;text-align:right;">@Html.LabelFor(x => x.UserName)</td>
                        <td style="width:80%;">@Html.TextBoxFor(x => x.UserName, new { @class = "w3-input w3-border", placeholder = "用戶名" })</td>
                    </tr>
                    <tr class="w3-padding">
                        <td style="vertical-align:middle;text-align:right;">@Html.LabelFor(x => x.Pwd)</td>
                        <td>@Html.PasswordFor(x => x.Pwd, new { @class = "w3-input w3-border", placeholder = "密  碼" })</td>
                    </tr>
                    <tr style="display:none;">
                        <td><input type="text" id="returnUrl" name="returnUrl" value="@Url.Encode(Request.Url.ToString())"/></td>
                    </tr>
                    <tr>
                        <td></td>
                        <td style="text-align:left;">
                            <div style="width:30%;"><button type="submit" class="w3-btn-block w3-teal">登錄</button></div>
                        </td>
                    </tr>
                </table>
        }
    </div>
    @*<script>
        function login() {

        }
    </script>*@
</body>
</html>

無權限提示頁面如下,也就是NoPemission:

@{
    Layout = null;
}
<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>NoPemission</title>
</head>
<body>
    <div> 
        您無權限操作該頁面!
    </div>
</body>
</html>

 

四、創建MyAuthorizeAttribute權限驗證類

創建MyAuthorizeAttribute類的目的是需要做身份驗證和權限驗證所用,通過繼承AuthorizeAttribute類來達到目的:

    public class MyAuthorizeAttribute : AuthorizeAttribute {
        //設置是否擁有某些權限的狀態碼
        private int _status = 0;
        protected override bool AuthorizeCore(HttpContextBase httpContext) {
//切記:一定要在設置_status的方法的第一行初始化該字段,否則會出現問題。因為針對同一Action來說,上一次訪問后完成后,下一次不同權限的賬戶又來訪問,如果不初始化該_status字段,那么該_status字段值還是上一次的舊值。
//估計綁定的Action的Controller實例還存在,沒有被回收,等到下一次調用該Controller時,就是會激活該Controller實例
_status=0;
//判斷當前登錄的用戶 是否已經登錄過且身份認證被通過 if (httpContext.User.Identity.IsAuthenticated) { //判斷當前的Roles字段是否是空,是空的,則說明該控制器或Action不需要角色權限控制 if (string.IsNullOrWhiteSpace(Roles)) return true; //通過半角逗號去獲取一個權限數組 string[] roles = Roles.Split(new string[] { "," }, StringSplitOptions.RemoveEmptyEntries); if (roles.Length <= 0) return true; //找到當前登錄的用戶 //httpContext.User.Identity.Name就是我們在Login驗證登錄時候設置的clogin.UserName。設置代碼為FormsAuthentication.SetAuthCookie(clogin.UserName, false); UserViewModel user = Data.Data.users.Where(u => u.UserName == httpContext.User.Identity.Name).SingleOrDefault(); //判斷當前登錄用戶是否擁有訪問該控制器或Action的權限,如果有,則返回true,可以訪問;如果沒有,則返回false,需要在OnAuthorization方法中設置該濾過器的Result。 if (roles.Contains(((int)user.Role).ToString())) return true; else { //無角色權限 通過設置_status,可以在OnAuthorization方法中做出相應的判斷,並設置相應的filterContext.Result _status = 10; //授權失敗 return false; } } //沒有登錄,則返回false,表明身份認證未通過,程序直接會跳轉到登錄頁面,登錄頁面可在web.config中設置 // < system.web > // < authentication mode = "Forms" > // < forms loginUrl = "~/Account/Login" timeout = "2" /> // </ authentication > // </ system.web > else return false; } public override void OnAuthorization(AuthorizationContext filterContext) { //執行父類的OnAuthorization方法 base.OnAuthorization(filterContext); //如果當前的_status有狀態變化,則返回相應的ActionResult。當前返回的是無權限查看的提示界面。 if (_status == 10) filterContext.Result = new RedirectToRouteResult(new System.Web.Routing.RouteValueDictionary(new { //需要跳轉的Controller controller = "Account", //需要跳轉的Action action = "NoPemission", //返回的字段說明 returnUrl = filterContext.HttpContext.Request.Url, returnMessage = "您無權限查看!" })); } }

 接着將MyAuthorizeAttribute特性類附加到StudentController的Action中,代碼如下:

    public class StudentController : Controller {

        //只要登錄成功,即可訪問
        [MyAuthorize]
        public ActionResult Index() {
            return View(Data.Data.students);
        }

        //需要登錄成功,且該用戶角色為 1:Admin 3:System 的才可訪問
        [MyAuthorize(Roles = "1,3")]
        public ActionResult FindOne(int? id) {
            return View("Index", Data.Data.students.Where(s => s.ID == id));
        }
    }

 

五、測試結果

首先我們將該站點的默認頁面設置為Student\Index,設置方法如下:

    public class RouteConfig {
        public static void RegisterRoutes(RouteCollection routes) {
            routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

            routes.MapRoute(
                name: "Default",
                url: "{controller}/{action}/{id}",
                defaults: new { controller = "Student", action = "Index", id = UrlParameter.Optional }
            );
        }
    }

接着運行程序:

因為站點啟動的時候,默認訪問的是Student控制器的Index行為方法,但我們在該Index行為方法附加了一個需要授權的特性MyAuthorize。因此,在沒有登錄之前去訪問Index行為方法的話,就會跳轉到我們在web.config配置文件中設置的登錄地址。因此,第一次出現在我們眼前的是一個登錄界面:

當我們輸入正確的用戶名和密碼,並點擊“登錄”按鈕后,就能進入Student控制器的Index行為方法了。如下圖所示:

因為我們是用admin賬號登錄的,該賬戶的權限是Admin,因此我們也可以訪問Student的FindOne行為方法。如下圖所示:

接下來讓我們點擊“退出”,注銷權限為Admin的admin賬戶,改用權限為Normal的normal賬戶試一下,看能否訪問被權限控制的Student的FindOne行為方法。

輸入賬戶名為normal,密碼為1的賬戶信息,並點擊“登錄”按鈕:

因為我們在Account的登錄驗證方法Login行為方法中標注登錄驗證成功后,就會跳轉到Student的FindOne行為方法,因此只要賬戶名和密碼輸入正確就能驗證是否能成功訪問FindOne。請看如下結果:

果然,因為我們在FindOne方法中標注了只有1(Admin)和3(System)權限才能訪問該方法,而Normal不在這些訪問權限之內。

由此可見,我們可以通過繼承AuthorizeAttribute類輕松地來達到身份驗證和授權的目的,如果不在權限之內,就是不讓你操作。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM