[Asp .Net MVC]撰寫自己的AuthorizeAttribute

.Net MVC本身就提供一套AuthorizeAttribute

但它常常與現有系統驗證、或是資料庫內的現有帳號資料表無法整合

所以今天與大家分享怎麼實作自己的AuthorizeAttribute

 

在MVC預設的AuthorizeAttribute中

你可以把Users理解成帳號

Roles理解成群組

有時後只想讓某些人進的去一個頁面、有時後希望根據群組進行權限設定

而假設我們是一間公司

通常以內部網站來說,帳號=網域名稱、群組=部門

 

另外

假設除了原生AuthorizeAttribute帶給我們的 Users 跟 Roles 以外

我們還需要多增加一個布林值欄位叫 IsPageAdminOnly = 是否此頁面只有管理者允許進入

 

而我們最後希望在Controller中呼叫的方式如下

 

所以我們會有一個現有的帳號資料表

假設結構如下

再跟大家提醒一次

Users = AD_ACCOUNT

Roles = Department

 

接下來我們會繼承原生的AuthorizeAttribute 假設我把新類別取名為PermissionFilter

而通常我們會開一個Filters資料夾來存放這個檔案

 

而PermissionFilter的完整程式碼如下

using Repository.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace SPIL.B2B.Portal.Filters
{
    public class PermissionFilter : AuthorizeAttribute
    {
        private static readonly string _unAuthUrl = @"..\Error\Unauthorized401";
        private static readonly string _forbiddenUrl = @"..\Error\Forbidden403";

        //有些頁面也許只想讓admin進入
        public bool IsPageAdminOnly = false;

        public override void OnAuthorization(AuthorizationContext filterContext)
        {
            //對有進網域的電腦而言不太可能發生
            if (filterContext == null)
            {
                filterContext.Result = new RedirectResult(_unAuthUrl);
            }

            if (AuthorizeCore(filterContext.HttpContext))
            {
                //驗證有過也不留cache
                SetCachePolicy(filterContext);
            }
            else if (filterContext.HttpContext.User.Identity.IsAuthenticated)
            {
                //通常 AuthorizeCore 沒過都是 403
                filterContext.Result = new RedirectResult(_forbiddenUrl);
            }
            else
            {
                //401 (對有進網域的電腦而言不太可能發生)
                filterContext.Result = new RedirectResult(_unAuthUrl);
            }
        }

        private void SetCachePolicy(AuthorizationContext filterContext)
        {
            //怕下一秒把這個人被改成Unauth,但因為上一秒他成功進來過,被瀏覽器cache permission,導致雖然已unauth卻還是進的來,所以set 0
            var cachePolicy = filterContext.HttpContext.Response.Cache;
            cachePolicy.SetProxyMaxAge(new TimeSpan(0));
            cachePolicy.AddValidationCallback(CacheValidateHandler, null);
        }

        private void CacheValidateHandler(HttpContext context, object data, ref HttpValidationStatus validationStatus)
        {
            validationStatus = OnCacheAuthorization(new HttpContextWrapper(context));
        }

        protected override bool AuthorizeCore(HttpContextBase httpContext)
        {
            var adAccount = httpContext.User.Identity.Name;

            //是不是admin only
            if (IsPageAdminOnly && !HasAdminAuth(adAccount))
            {
                return false;
            }
            else
            {
                //其他用個人AD和部門權限判斷
                return HasAuth(adAccount);
            }
        }

        private bool HasAdminAuth(string adAccount)
        {
            //為了方便講解--------------------------------------------------------
            var admins = new List<string> { @"MyCompany\John" };
            //--------------------------------------------------------------------

            return admins.Contains(adAccount) ? true : false;
        }

        private bool HasAuth(string adAccount)
        {
            //取得允許名單
            var allowUsers = Users.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
            var allowRoles = Roles.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);

            if (allowUsers.Any() && !allowUsers.Contains(adAccount))
            {
                return false;
            }

            if (allowRoles.Any() && !HasRoleAuth(adAccount, allowRoles))
            {
                return false;
            }

            return true;
        }

        private bool HasRoleAuth(string adAccount, String[] allowRoles)
        {
            //為了方便講解--------------------------------------------------------
            var employees = new List<Employee>
            {
                new Employee { AD_ACCOUNT=@"MyCompany\John", Department="A部門", Name="John" },
                new Employee { AD_ACCOUNT=@"MyCompany\Mary", Department="B部門", Name="Mary" },
                new Employee { AD_ACCOUNT=@"MyCompany\Tom", Department="C部門", Name="Tom" },
            };
            //--------------------------------------------------------------------

            var target = employees.Where(q => q.AD_ACCOUNT == adAccount).ToList();

            if (target.Any() && allowRoles.Contains(target.First().Department))
            {
                return true;
            }
            else
            {
                return false;
            }
        }
    }
}