1. 程式人生 > >MVC身份驗證及許可權管理

MVC身份驗證及許可權管理

MVC自帶的ActionFilter

在Asp.Net WebForm的中要做到身份認證微軟為我們提供了三種方式,其中最常用的就是我們的Form認證,需要配置相應的資訊。例如下面的配置資訊:

<authentication mode="Forms">
    <forms loginUrl="Login.aspx" defaultUrl="Default.aspx" protection="All" />
</authentication>
<authorization>
    <deny users="?"/>
    <allow
users="*"/>
</authorization>

說明我們登入頁面是Login.aspx,登入成功後的預設頁面是Default.aspx,而我們使用者資訊採用驗證和加密兩種方式。而且最重要的 是我們要寫好授權方式(下面的授權一定要寫否則只說明使用Forms認證然後設定相關屬性是沒有用的),拒絕所有匿名使用者,只有登入使用者可以正常訪問。這 樣之後我們設定點選登入按鈕將使用者名稱寫進cookie(也就是執行FormsAuthentication.SetAuthCookie(name, false);)就可以了。

在Asp.Net MVC中我們同樣可以使用Forms認證,但是如果你按照WebForm中的做法去做就不行了。例如你這樣配置資訊:

<authentication mode="Forms">
    <forms loginUrl="~/Account/Login" defaultUrl="~/Home/Index" protection="All"/>
</authentication>
<authorization>
    <deny users="?"/>
    <allow users="*"/>
</authorization>

你在Login.aspx中設定登入來觸發AccountController中的Logon來登入,其中Logon程式碼:

public ActionResult Logon(string name,string password)    
{    
    if (name == "jianxin160" && password == "160796")    
    {    
        FormsAuthentication.SetAuthCookie(name, false);    
        return Redirect("~/Home/Index");    
    }    
    else
    {    
        return Redirect("/");    
    }    
}

這樣的操作之後你會發現你的Logon是不會執行的。原因是什麼呢?怎麼同樣的設定為什麼到了MVC中就不行了?原因就是二者機制不同,因為你設定的授權方式讓Logon無法訪問了。那麼我們怎麼來做呢?

其實在Asp.Net MVC中我們有更好的方式來做這一切,我們不需要授權方式,也就是說我們的配置資訊像這樣:

<authentication mode="Forms">
    <forms loginUrl="~/Account/Login" defaultUrl="~/Home/Index" protection="All"/>
</authentication>

不需要說明匿名使用者不能登入等。當然了,你會發現僅僅就這樣做肯定不行的我們還要換一種方式告訴系統哪些是需要登入才能訪問的。你或許 想,o(︶︿︶)o 唉,那太麻煩了吧。其實不是這樣的,很簡單,我們只需要在需要認證的Action上標記[Authorize]就可以了。例如我在Home資料夾中有兩個 頁面Index和Home,我現在想讓Index經過認證才能訪問,而Home不需要,那麼只需要給Index這個Action標記 [Authorize],也就是:

[Authorize]    
public ActionResult Index()    
{    
    return View();    
}    

public ActionResult Home()    
{    
    return View();    
}

這樣Index就必須登入之後才能訪問,而Home是不需要登入的。如果你需要進行角色授權那麼您就可以在標記Authorize的時候指明角色 (例如[Authorize(Role=Administrators)] ),不過您這是就必須使用微軟給我們提供的Membership機制了,因為您的Role不可能是平白無故有的,而是存在於對應的資料庫中的,這個我在另 一篇部落格中提到過就不多說了。

自定義ActionFilter

有時候這樣的認證或許您還不能夠滿足,或者說您覺得不夠靈活,那麼也沒有關係,Asp.Net MVC是允許您自定義ActionFilter的。例如我現在自定義身份認證:

using System;   
using System.Collections.Generic;   
using System.Linq;   
using System.Web;   
using System.Web.Mvc;   
using System.Web.Security;    

namespace FormFormsAuthenticationMvc   
{   
    public class RequiresAuthenticationAttribute:ActionFilterAttribute   
    {   
        public override void  OnActionExecuting(ActionExecutingContext filterContext)   
        {   
            if (!filterContext.HttpContext.User.Identity.IsAuthenticated)   
            {   
                string returnUrl = filterContext.HttpContext.Request.Url.AbsolutePath;   
                string redirectUrl = string.Format("?ReturnUrl={0}", returnUrl);   
                string loginUrl = FormsAuthentication.LoginUrl + redirectUrl;   
                filterContext.HttpContext.Response.Redirect(loginUrl, true);   
            }   
        }    
    }   
}

如果需要進行使用者管理,我再定義角色相關的Filter:

using System;    
using System.Collections.Generic;    
using System.Linq;    
using System.Web;    
using System.Web.Mvc;    
using System.Web.Security;    

namespace MvcApplication1.MyClass    
{    
    public class RequiresRoleAttribute:ActionFilterAttribute    
    {    
        public string Role { get; set; }    

        public override void OnActionExecuting(ActionExecutingContext filterContext)    
        {    
            if (!string.IsNullOrEmpty(Role))    
            {    
                if (!filterContext.HttpContext.User.Identity.IsAuthenticated)    
                {    
                    string returnUrl = filterContext.HttpContext.Request.Url.AbsolutePath;    
                    string redirectUrl = string.Format("?ReturnUrl={0}", returnUrl);    
                    string loginUrl = FormsAuthentication.LoginUrl + redirectUrl;    
                    filterContext.HttpContext.Response.Redirect(loginUrl, true);    
                }    
                else
                {    
                    bool isAuthenticated = filterContext.HttpContext.User.IsInRole(Role);    
                    if (!isAuthenticated)    
                    {    
                    throw new UnauthorizedAccessException("You have no right to view the page!");    
                    }    
                }    
            }    
            else
            {    
                throw new InvalidOperationException("No Role Specified!");    
            }    
        }    
    }    
}

其實您會發現上面兩個Attribute其實MVC自帶的Authorized已經解決了,這裡主要告訴大家如果有需要您是可以擴充套件的。

好了,今天就到這裡吧!原始碼下載:FormFormsAuthenticationMvc

ASP.NET MVC 建立 ASP.NET 基礎之上,很多 ASP.NET 的特性(如窗體身份驗證、成員資格)在 MVC 中可以直接使用。本文旨在提供可參考的程式碼,不會涉及這方面太多理論的知識。

本文僅使用 ASP.NET 的窗體身份驗證,不會使用它的 成員資格(Membership) 和 角色管理 (RoleManager),原因有二:一是不靈活,二是和 MVC 關係不太。
一、示例專案

image

User.cs 是模型檔案,其中包含了 User 類:

public class User
{
    public int ID { get; set; }
    public string Name { get; set; }
    public string Password { get; set; }
    public string[] Roles { get; set;  }
}

UserRepository 為資料存取類,為了演示方便,並沒有連線資料庫,而是使用一個數組來作為資料來源:

public class UserRepository
{
    private static User[] usersForTest = new[]{
        new User{ ID = 1, Name = "bob", Password = "bob", Roles = new []{"employee"}},
        new User{ ID = 2, Name = "tom", Password = "tom", Roles = new []{"manager"}},
        new User{ ID = 3, Name = "admin", Password = "admin", Roles = new[]{"admin"}},
    };

    public bool ValidateUser(string userName, string password)
    {
        return usersForTest
            .Any(u => u.Name == userName && u.Password == password);
    }

    public string[] GetRoles(string userName)
    {
        return usersForTest
            .Where(u => u.Name == userName)
            .Select(u => u.Roles)
            .FirstOrDefault();
    }

    public User GetByNameAndPassword(string name, string password)
    {
        return usersForTest
            .FirstOrDefault(u => u.Name == name && u.Password == password);
    }
}

二、使用者登入及身份驗證
方式一

修改 AccountController:原有 AccountController 為了實現控制反轉,對窗體身份驗證進行了抽象。為了演示方便,我去除了這部分(以及註冊及修改密碼部分):

public class AccountController : Controller
{
    private UserRepository repository = new UserRepository();

    public ActionResult LogOn()
    {
        return View();
    }

    [HttpPost]
    public ActionResult LogOn(LogOnModel model, string returnUrl)
    {
        if (ModelState.IsValid)
        {
            if (repository.ValidateUser(model.UserName, model.Password))
            {
                FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe);
                if (!String.IsNullOrEmpty(returnUrl)) return Redirect(returnUrl);
                else return RedirectToAction("Index", "Home");
            }
            else
                ModelState.AddModelError("", "使用者名稱或密碼不正確!");
        }
        return View(model);
    }

    public ActionResult LogOff()
    {
        FormsAuthentication.SignOut();
        return RedirectToAction("Index", "Home");
    }
}

修改 Global.asax:

public class MvcApplication : System.Web.HttpApplication
{
    public MvcApplication()
    {
        AuthorizeRequest += new EventHandler(MvcApplication_AuthorizeRequest);
    }

    void MvcApplication_AuthorizeRequest(object sender, EventArgs e)
    {
        IIdentity id = Context.User.Identity;
        if (id.IsAuthenticated)
        {
            var roles = new UserRepository().GetRoles(id.Name);
            Context.User = new GenericPrincipal(id, roles);
        }
    }
    //...
}

給 MvcApplication 增加建構函式,在其中增加 AuthorizeRequest 事件的處理函式。

程式碼下載:Mvc-FormsAuthentication-RolesAuthorization-1.rar (243KB)
方式二

此方式將使用者的角色儲存至使用者 Cookie,使用到了 FormsAuthenticationTicket。

修改 AccountController:

public class AccountController : Controller
{
    private UserRepository repository = new UserRepository();

    public ActionResult LogOn()
    {
        return View();
    }

    [HttpPost]
    public ActionResult LogOn(LogOnModel model, string returnUrl)
    {
        if (ModelState.IsValid)
        {
            User user = repository.GetByNameAndPassword(model.UserName, model.Password);
            if (user != null)
            {
                FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(
                    1,
                    user.Name,
                    DateTime.Now,
                    DateTime.Now.Add(FormsAuthentication.Timeout),
                    model.RememberMe,
                    user.Roles.Aggregate((i,j)=>i+","+j)
                    );                    
                HttpCookie cookie = new HttpCookie(
                    FormsAuthentication.FormsCookieName,
                    FormsAuthentication.Encrypt(ticket));
                Response.Cookies.Add(cookie);

                if (!String.IsNullOrEmpty(returnUrl)) return Redirect(returnUrl);
                else return RedirectToAction("Index", "Home");
            }
            else
                ModelState.AddModelError("", "使用者名稱或密碼不正確!");
        }
        return View(model);
    }

    public ActionResult LogOff()
    {
        FormsAuthentication.SignOut();
        return RedirectToAction("Index", "Home");
    }
}

修改 Global.asax:

public class MvcApplication : System.Web.HttpApplication
{
    public MvcApplication()
    {
        AuthorizeRequest += new EventHandler(MvcApplication_AuthorizeRequest);
    }

    void MvcApplication_AuthorizeRequest(object sender, EventArgs e)
    {
        var id = Context.User.Identity as FormsIdentity;
        if (id != null && id.IsAuthenticated)
        {
            var roles = id.Ticket.UserData.Split(',');
            Context.User = new GenericPrincipal(id, roles);
        }
    }
    //...
}

程式碼下載:Mvc-FormsAuthentication-RolesAuthorization-2.rar (244KB)
三、角色許可權

使用任一種方式後,我們就可以在 Controller 中使用 AuthorizeAttribute 實現基於角色的許可權管理了:

[Authorize(Roles = "employee,manager")]
public ActionResult Index1()
{
    return View();
}
[Authorize(Roles = "manager")]
public ActionResult Index2()
{
    return View();
}
[Authorize(Users="admin", Roles = "admin")]
public ActionResult Index3()
{
    return View();
}

四、簡要說明

MVC 使用 HttpContext.User 屬性進行來進行實現身份驗證及角色管理,同樣 AuthorizeAttribute 也根據 HttpContext.User 進行角色許可權驗證。

因些不要在使用者登入後,將相關使用者資訊儲存在 Session 中(網上經常看到這種做法),將使用者儲存在 Session 中是一種非常不好的做法。

也不要在 Action 中進行角色許可權判斷,應該使用 AuthorizeAttribute 或它的子類,以下的方式都是錯誤的:

public ActionResult Action1()
{
    if (Session["User"] == null) { /**/}
    /**/
}
public ActionResult Action2()
{
    if (User.Identity == null) { /**/}
    if (User.Identity.IsAuthenticated == false) { /**/}
    if (User.IsInRole("admin") == false) { /**/}
    /**/
}