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 关系不太。
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) { /**/} /**/ }