[一步一步MVC]第二回:還是ActionFilter,實現對業務邏輯的統一Authorize處理

作者: Anytao  來源: 博客園  發布時間: 2009-12-25 10:17  閱讀: 3686 次  推薦: 0   原文鏈接   [收藏]  

系列文章導航:

[一步一步MVC]第一回:使用ActionSelector控制Action的選擇

[一步一步MVC]第二回:還是ActionFilter,實現對業務邏輯的統一Authorize處理

[一步一步MVC]第三回:MVC范例大觀園

[一步一步MVC]第四回:漫談ActionLink,有時“胡攪蠻纏”

[一步一步MVC]第五回:讓TagBuilder豐富你的HtmlHelper

[一步一步MVC]第六回:什么是MVC(上)?

 

由問題引出

在ASP .NET MVC中,以友好的URL訪問資源是MVC吸引眼球的特色之一,但是隨之而來對于Authorize問題的處理變得令人令人頭痛。例如假設我們有一個獲取Book信息的Action,定義在BookController中:

public BookController : Controller
{
  // Release : code01, 2009/04/22
  // Author : Anytao, http://www.anytao.com

  public ActionResult Index(int id)
  {
    Book model = (new IBookService()).GetBook(id);
    return View(model);
  }
}

那么,我們可以通過http://anytao.net/Book/index/1,來訪問id為1的Book(例如該書是《你必須知道的.NET》,哈哈,廣告嫌疑)。在沒有任何特別處理的情況下,對于該書的訪問是“不設防”的。任何用戶可以通過http://anytao.net/Book/index/1實現對《你必須知道的.NET》信息的訪問。那么訪問的資源如果是http://anytao.net/Secret/index/1,顯然我的秘密無一例外的對外公開了。

言之此處,我們的問題已經明白無疑,那么應該如何處理呢?我們可以很容易的想到通過以下的方式進行處理:

// Release : code02, 2009/04/22                    
// Author  : Anytao, http://www.anytao.com
public ActionResult Index(int id)
{
    if (new IAuthorizeService().IsBookAuthorized(id, User.Identity.Name))
    {
        Book model = (new IBookService()).GetBook(id);

        return View(model);
    }
    else
    {
        return View("NotValid");
    }
}

顯然,我通過IsBookAuthorized對GetBook服務的訪問有效性進行控制,通過User的Name在數據庫或者其他資源存儲進行查找, 然后根據IsBookAuthorized結果進行是否訪問的控制,顯然不合法的用戶將被導航到NotValid頁,提示你是非法用戶。

這種方式顯然是最容易想到的辦法,而且也廣泛存在于我們實際的應用中,例如NerdDinner范例中也是通過這種方式進行Authorize控制處理的,例如:

[Authorize]
public ActionResult Edit(int id) {

    Dinner dinner = dinnerRepository.GetDinner(id);

    if (!dinner.IsHostedBy(User.Identity.Name))
        return View("InvalidOwner");

    return View(new DinnerFormViewModel(dinner));
}

然而這種方式存在或多或少的問題,例如:

  • IsBookAuthorized將分散于不同的Action或者BLL層中,對于統一的Authorized管理帶來問題。
  • 實際的Authorized執行已經滲透到Action或者Serivce內部,我們更期待在Action調用之前對此已經進行了處理。

思考的瞬間

那么,統一的處理該如何著手實現更優雅的、更統一的Authorize處理呢?顯然MVC自帶的Authorize特性,為我們提供了可選擇的思路:

[Authorize(Users = "Anytao")]
public ActionResult Edit(int id)
{
    return View();
}

Authorize標記通過對于Users或者Roles的定義,來對Edit Action的執行進行“預”Authorize授權,那么登陸用戶為Anytao的用戶才有權對BookController Edit進行訪問,否則將無權訪問。顯然,這種方式對于滿足我們

  • 統一Authorize處理
  • 在Action調用之前進行授權驗證

的目標是統一的。所以,我們可以借助這種方式實現自定義的統一Authorize處理方案。

統一Authorize解決方案

有了指導方針,我們就可以有的放肆了,我們的方案同樣是應用ActionFilter實現對Authorize處理,上次的范例是{[一步一步MVC]第四回:使用ActionSelector控制Action的選擇}。顯然我們可以在OnActionExecuting事件中對Action進行“預”處理,將關于Authorize的驗證過程統一在OnActionExecuting中進行,就可以對標記的Action實現調用之前的過濾了,所以我們首先實現一個AuthorizeAttributeBase,例如:

// Release : code03, 2009/04/22                    
// Author  : Anytao, http://www.anytao.com
public abstract class AuthorizeAttributeBase : ActionFilterAttribute
{
    public AuthorizeAttributeBase()
    { 
    }

    public AuthorizeAttributeBase(string key)
    {
        Key = key;
    }

    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        // Authorize handler 
    }

    public string Key { get; set; }

    protected abstract bool IsAuthorized(int id);
}

而具體的驗證則在具體實現類中,例如我們對Book的驗證:

// Release : code04, 2009/04/22                    
// Author  : Anytao, http://www.anytao.com
public class BookAuthorizeAttribute : AuthorizeAttributeBase
{
    protected override bool IsAuthorized(int id)
    {
        return (new IAuthorizeService()).IsBookAuthorized(id);
    }
}

對于驗證的處理必須解決兩方面的問題:

  • 在AuthorizeAttributeBase中獲取待過濾Action中的參數(Index(int id)),一般而言我們需要對id進行驗證,那么傳入id的值該如何處理。
  • 在AuthorizeAttributeBase對于非法用戶的處理,一般而言就是導航到NotValid頁面。

在OnActionExecuting中獲取Action參數

我們采用的方法是通過filterContext的ActionParameters來獲取參數值,通過參數的Key來獲取其值,例如:

if (filterContext.ActionParameters.ContainsKey(key))
{
    value = int.Parse(filterContext.ActionParameters[key].ToString());
}

在OnActionExecuting中導航到不同的View

這也是一個簡單的處理,我們只要指定好filterContext的Result為指定的ViewResult即可實現我們的目標:

filterContext.Result = new ViewResult{
    ViewName = "NotValid"
};

解決了上述問題,就基本實現了對Authorize進行統一處理的目標,至于具體的Authorize邏輯,不同的業務可以在不同的業務層進行封裝。例如對于Book資源的處理可以統一在IBookService中,對于User資源的處理可以統一在IUserService中(不過顯然我們已經有了MVC自帶的Authorize,不必重復),對于其他的資源也相應的處理在不同的業務層中。

下面是AuthorizeAttributeBase和BookAuthorizeAttribute的完整代碼:

// Release : code03, 2009/04/22                    
// Author  : Anytao, http://www.anytao.com
public abstract class AuthorizeAttributeBase : ActionFilterAttribute
{
    public AuthorizeAttributeBase()
    { 
    }

    public AuthorizeAttributeBase(string key)
    {
        Key = key;
    }

    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        string key = string.IsNullOrEmpty(Key) ? "id" : Key;

        int id;

        if (filterContext.ActionParameters.ContainsKey(key))
        {
            if (!int.TryParse(filterContext.ActionParameters[key].ToString(), out id))
            {
                id = 0;
            }
        }
        else
        {
            id = 0;
        }

        if (id > 0)
        {
            if (IsAuthorized(id))
            {
                base.OnActionExecuting(filterContext);
            }
            else
            {
                filterContext.Result = new ViewResult{
                    ViewName = "NotValid"
                };


            }
        }
        else
        {
            filterContext.Result = new ViewResult{
                    ViewName = "NotValid"
                };
        }
    }

    public string Key { get; set; }

    protected abstract bool IsAuthorized(int id);
}

接下來就是如何應用了。

在Controller中應用統一Authorize處理

下面是我們的應用,還是對于http://anytao.net/Book/index/1的訪問,我們可以像下面這樣應用:

// Release : code05, 2009/04/22                    
// Author  : Anytao, http://www.anytao.com
[BookAuthorize(Key="id")]
public ActionResult Index(int id)
{
    Book model = (new IBookService()).GetBook(id);

    return View(model);
}

對比前后的兩種方案,我想孰優孰劣顯而易見。BookAuthorize顯然以更優雅的方式實現了對于Authorize這回事兒的處理,也基本達到了原來的目標。我們的驗證邏輯沒有散落在系統四處,如何同時需要對Book的Index進行多個邏輯的驗證,我們的方式也變得很簡單,例如:

// Release : code05, 2009/04/22                    
// Author  : Anytao, http://www.anytao.com
[BookAuthorize(Key="id"), TaskAuthorize(Key="id")]
public ActionResult Index(int id)
{
    Book model = (new IBookService()).GetBook(id);

    return View(model);
}

不過,我們需要對id的復用進行一點思考,不過那已經是另外一回兒事兒了。對本文而言,我已經達到了目標。當然,這也許不是最好的方案,所以我期待您的更好方案,因為技術需要切磋和共享。

又是一個小技巧,希望給你幫助。

代碼下載[anytao_mvc_actionauthorize],更多關注,盡在anytao.net/blog
0
0
 
標簽:MVC
 
 

文章列表

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()