概述
许多 Web 应用程序都要求能够限制对某些资源(例如特定页面)的访问,以确保只有经过身份验证的用户才能访问这些资源。 ASP.NET MVC 的默认 Web 应用程序项目模板提供了一个控制器以及一些数据模型和视图,您可使用这些组件为应用程序添加 ASP.NET 窗体身份验证功能。 借助该内置功能,用户可以注册、登录和注销,以及更改自己的密码。 对于许多应用程序,此功能可提供足够的用户身份验证级别。
页面控件引用
在MVC 3.0的项目模板里面的Shared文件夹中,我们可以看到名为_LogOnPartial.cshtml的页面,打开页面代码
@if(Request.IsAuthenticated) { <text>欢迎使用 <b>@Context.User.Identity.Name</b>! [ @Html.ActionLink("注销", "LogOff", "User") ]</text> } else { @:[ @Html.ActionLink("登录", "LogOn", "User") ] }
我们可以看到该页面只是根据获取系统中的用户登录名,<b>@Context.User.Identity.Name</b>判断是登录还是注销的。
然后我们看看在什么地方用到了该页面?
MVC 3.0的项目模板的Shared文件夹中,我们在_Layout.cshtml页面中找到了,这个页面就是项目的整个母版页,相当于.Master页面。
代码
<div id="logindisplay"> @Html.Partial("_LogOnPartial") </div>
这样简单的引用就可以将其他页面作为一个控件引用到该页面来。
实现登录
在前面的判断用户登录名时,我们可以看到有如下代码
@:[ @Html.ActionLink("登录", "LogOn", "User") ]
该代码指定登录在UserController的LogOn方法。
创建用户Model类
public class User { public int ID { get; set; } [DisplayName("姓名")] [Required(ErrorMessage = "姓名不能为空")] public string Name { get; set; } [DisplayName("密码")] [Required] [DataType(DataType.Password)] public string Password { get; set; } [Display(Name = "记住我?")] public bool RememberMe { get; set; } public Roles Roles { get; set; } }
创建角色Model类,便于后面页面角色的管理
代码:
public class Roles { public int ID { get; set; } public string Name { get; set; } }
当前一个用户对应一个角色,不是一对多。后面有空会做个例子出来。
所以我们创建UserController在UserController添加LogOn方法
代码
// ************************************** // URL: /User/LogOn // ************************************** public ActionResult LogOn() { return View(); }
创建视图
代码
@model MvcApplication.Models.User @{ ViewBag.Title = "用户登录"; Layout = "~/Views/Shared/_Layout.cshtml"; } <h2> 用户登录</h2> <script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script> <script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script> @using (Html.BeginForm()) { @Html.ValidationSummary(true) <fieldset> <legend>登录信息</legend> <div class="editor-label"> @Html.LabelFor(model => model.Name) </div> <div class="editor-field"> @Html.EditorFor(model => model.Name) @Html.ValidationMessageFor(model => model.Name) </div> <div class="editor-label"> @Html.LabelFor(model => model.Password) </div> <div class="editor-field"> @Html.EditorFor(model => model.Password) @Html.ValidationMessageFor(model => model.Password) </div> <div class="editor-label"> @Html.LabelFor(model => model.RememberMe) </div> <div class="editor-field"> @Html.EditorFor(model => model.RememberMe) @Html.ValidationMessageFor(model => model.RememberMe) </div> <p> <input type="submit" value="登录" /> </p> </fieldset> } <div> @Html.ActionLink("返回主页", "Index") </div>
有了界面,但是我们还没有对数据的操作
业务逻辑接口
创建一个名为IUserBusiness接口
public interface IUserBusiness { /// <summary> /// 获取用户角色 /// </summary> /// <param name="userName"></param> /// <returns></returns> Roles GetRoles(string userName); /// <summary> /// 根据用户名和密码获取用户信息 /// </summary> /// <param name="name"></param> /// <param name="password"></param> /// <returns></returns> User GetByNameAndPassword(string name, string password); /// <summary> /// 登录 /// </summary> /// <param name="userName"></param> /// <param name="createPersistentCookie"></param> void SignIn(string userName, bool createPersistentCookie); /// <summary> /// 注销 /// </summary> void SignOut(); }
实现接口
public class UserBusiness : IUserBusiness { /// <summary> /// 作为模拟的数据集 /// </summary> private static User[] UserList = new[] { new User{ ID = 1, Name = "张三", Password = "111111", Roles = new Roles{ID=101,Name = "employee"}}, new User{ ID = 2, Name = "李四", Password = "111111", Roles = new Roles{ID =102,Name = "manager"}}, new User{ ID = 3, Name = "admin", Password = "admin", Roles = new Roles{ID = 103,Name = "admin"}} }; /// <summary> /// 根据用户名获取角色 /// </summary> /// <param name="userName"></param> /// <returns></returns> public Roles GetRoles(string userName) { return UserList .Where(o=> o.Name == userName) .Select(o => o.Roles) .FirstOrDefault(); } /// <summary> /// 根据用户名和密码获取用户 /// </summary> /// <param name="name"></param> /// <param name="password"></param> /// <returns></returns> public User GetByNameAndPassword(string name, string password) { return UserList .FirstOrDefault(u => u.Name == name && u.Password == password); } /// <summary> /// 登录 /// </summary> /// <param name="userName"></param> /// <param name="createPersistentCookie"></param> public void SignIn(string userName, bool createPersistentCookie) { if (String.IsNullOrEmpty(userName)) throw new ArgumentException("值不能为 null 或为空。", "userName"); FormsAuthentication.SetAuthCookie(userName, createPersistentCookie); } /// <summary> /// 注销 /// </summary> public void SignOut() { FormsAuthentication.SignOut(); } }
上面的角色获取和用户名密码获取都可以变化为数据库里面的数据。而登录只是把获取的数据添加到了cookies里面。注销亦然是使用了该机制。
所以上面登录中的
FormsAuthentication.SetAuthCookie(userName, createPersistentCookie);
完全可以换成
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket( 1, user.Name, DateTime.Now, DateTime.Now.Add(FormsAuthentication.Timeout), user.RememberMe, user.Name ); HttpCookie cookie = new HttpCookie( FormsAuthentication.FormsCookieName, FormsAuthentication.Encrypt(ticket)); Response.Cookies.Add(cookie);
同样实现了登录的操作。
方法调用
我们在实现操作的时候要调用IUserBusiness接口中的相应方法来实现。
所以参考微软的做法,重写了Initialize方法,把IUserBusiness接口作为一个属性在程序中使用。
代码:
public IUserBusiness UserBusiness { get; set; } protected override void Initialize(RequestContext requestContext) { if (UserBusiness == null) { UserBusiness = new UserBusiness(); } base.Initialize(requestContext); }
这样,每次加载的时候都会根据相应的方法去实例IUserBusiness。
在UserController中新建Post的LogOn方法
代码
[HttpPost] public ActionResult LogOn(User user, string returnUrl) { if (ModelState.IsValid) { if (UserBusiness.GetByNameAndPassword(user.Name, user.Password)!=null) { UserBusiness.SignIn(user.Name, user.RememberMe); if (Url.IsLocalUrl(returnUrl)) { return Redirect(returnUrl); } else { return RedirectToAction("Index", "Home"); } } else { ModelState.AddModelError("", "提供的用户名或密码不正确。"); } } // 如果我们进行到这一步时某个地方出错,则重新显示表单 return View(user); }
效果
点击登录
登陆后
注销用户
在UserController中添加LogOff的注销方法
// ************************************** // URL: /User/LogOff // ************************************** public ActionResult LogOff() { UserBusiness.SignOut(); return RedirectToAction("Index", "Home"); }
同样调用了 业务逻辑里面的注销方法。
权限判断
在之前我们加入了Roles也在业务逻辑里面添加了获取Role的方法,那么我们在什么时候来取得该用户的Role呢?
我觉得是在页面验证之后要授权时,该方法在全局的Global.asax中,我们需要委托该事件。
委托时我们再去根据当前用户获取角色,或者此时早已经将角色获取好了,只是加入到系统的Context之用。
代码
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 UserBusiness().GetRoles(id.Name); string[] rolelist = new string[] { roles.Name }; Context.User = new GenericPrincipal(id, rolelist); } }
然后我们在UserController只用需要权限的方法上加上对应的[Authorize]标志即可
如代码
//新建 // GET: /User/Create [Authorize] public ActionResult Create() { return View(); }
如果你觉得,这样只是对于登录后所有的人有效,而你需要对指定的角色有效,你可以尝试用下面的方法。
代码如下
//新建 // GET: /User/Create [Authorize(Roles="admin")] public ActionResult Create() { return View(); }
此时如果你的用户不是admin那么你就请求不了该Create方法
点击之后
同样你可以重写该错误方法,让他弹出错误页面或者什么,那就看个人喜好了。
总结
其实像这种方法不仅仅是MVC特有的,在很多框架或者程序里面都用了类似的方法,总之还是借用了微软本身的这种窗体身份验证的特性。上面的方法只是一种思想,不一定会在程序中用到,现在很多老鸟都已经摆脱这种老套的身份验证了,对于新手菜鸟们拿来学习一下我觉得还是无可厚非的。