ASP.NET MVC 3
使用Forms身份验证
身份验证流程
一、用户登录
1、验证表单:ModelState.IsValid
2、验证用户名和密码:通过查询数据库验证
3、如果用户名和密码正确,则在客户端保存Cookie以保存用户登录状态:SetAuthCookie
1):从数据库中查出用户名和一些必要的信息,并把额外信息保存到UserData中
2):把用户名和UserData保存到 FormsAuthenticationTicket 票据中
3):对票据进行加密 Encrypt
4):将加密后的票据保存到Cookie发送到客户端
4、跳转到登录前的页面
二、验证登录
1、在Global中注册PostAuthenticateRequest事件函数,用于解析客户端发过来的Cookie数据
1):通过 HttpContext.Current.User.Identity 判断用户是否登录(FormsIdentity,IsAuthenticated,AuthenticationType)
2):从HttpContext 的Request的Cookie中解析出Value,解密得到 FormsAuthenticationTicket 得到UserData
2、角色验证
在Action加入 Authorize特性,可以进行角色验证
在 HttpContext.Current.User 的 IsInRole 方法进行角色认证(需要重写)
下面是代码,以上用到的所有验证的类都进行重载
一、首先是用户用户身份认证的 IPrincipal
这里抽象出通用属性,定义两个 IPrincipal
//通用的用户实体 public class MyFormsPrincipal<TUserData> : IPrincipal where TUserData : class, new() { //当前用户实例 public IIdentity Identity { get; private set; } //用户数据 public TUserData UserData { get; private set; } public MyFormsPrincipal(FormsAuthenticationTicket ticket, TUserData userData) { if (ticket == null) throw new ArgumentNullException("ticket"); if (userData == null) throw new ArgumentNullException("userData"); Identity = new FormsIdentity(ticket); UserData = userData; } //角色验证 public bool IsInRole(string role) { var userData = UserData as MyUserDataPrincipal; if (userData == null) throw new NotImplementedException(); return userData.IsInRole(role); } //用户名验证 public bool IsInUser(string user) { var userData = UserData as MyUserDataPrincipal; if (userData == null) throw new NotImplementedException(); return userData.IsInUser(user); } }
通用实体里面可以存放数据实体,并且把角色验证和用户验证放到了具体的数据实体里面
//存放数据的用户实体 public class MyUserDataPrincipal : IPrincipal { //数据源 private readonly MingshiEntities mingshiDb = new MingshiEntities(); public int UserId { get; set; } //这里可以定义其他一些属性 public List<int> RoleId { get; set; } //当使用Authorize特性时,会调用改方法验证角色 public bool IsInRole(string role) { //找出用户所有所属角色 var userroles = mingshiDb.UserRole.Where(u => u.UserId == UserId).Select(u => u.Role.RoleName).ToList(); var roles = role.Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries); return (from s in roles from userrole in userroles where s.Equals(userrole) select s).Any(); } //验证用户信息 public bool IsInUser(string user) { //找出用户所有所属角色 var users = user.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); return mingshiDb.User.Any(u => users.Contains(u.UserName)); } [ScriptIgnore] //在序列化的时候忽略该属性 public IIdentity Identity { get { throw new NotImplementedException(); } } }
二、用于验证和设置Cookie的 FormsAuthentication
//身份验证类 public class MyFormsAuthentication<TUserData> where TUserData : class, new() { //Cookie保存是时间 private const int CookieSaveDays = 14; //用户登录成功时设置Cookie public static void SetAuthCookie(string username, TUserData userData, bool rememberMe) { if (userData == null) throw new ArgumentNullException("userData"); var data = (new JavaScriptSerializer()).Serialize(userData); //创建ticket var ticket = new FormsAuthenticationTicket( 2, username, DateTime.Now, DateTime.Now.AddDays(CookieSaveDays), rememberMe, data); //加密ticket var cookieValue = FormsAuthentication.Encrypt(ticket); //创建Cookie var cookie = new HttpCookie(FormsAuthentication.FormsCookieName, cookieValue) { HttpOnly = true, Secure = FormsAuthentication.RequireSSL, Domain = FormsAuthentication.CookieDomain, Path = FormsAuthentication.FormsCookiePath, }; if (rememberMe) cookie.Expires = DateTime.Now.AddDays(CookieSaveDays); //写入Cookie HttpContext.Current.Response.Cookies.Remove(cookie.Name); HttpContext.Current.Response.Cookies.Add(cookie); } //从Request中解析出Ticket,UserData public static MyFormsPrincipal<TUserData> TryParsePrincipal(HttpRequest request) { if (request == null) throw new ArgumentNullException("request"); // 1. 读登录Cookie var cookie = request.Cookies[FormsAuthentication.FormsCookieName]; if (cookie == null || string.IsNullOrEmpty(cookie.Value)) return null; try { // 2. 解密Cookie值,获取FormsAuthenticationTicket对象 var ticket = FormsAuthentication.Decrypt(cookie.Value); if (ticket != null && !string.IsNullOrEmpty(ticket.UserData)) { var userData = (new JavaScriptSerializer()).Deserialize<TUserData>(ticket.UserData); if (userData != null) { return new MyFormsPrincipal<TUserData>(ticket, userData); } } return null; } catch { /* 有异常也不要抛出,防止攻击者试探。 */ return null; } } }
三、用于验证角色和用户名的Authorize特性
//验证角色和用户名的类 public class MyAuthorizeAttribute : AuthorizeAttribute { protected override bool AuthorizeCore(System.Web.HttpContextBase httpContext) { var user = httpContext.User as MyFormsPrincipal<MyUserDataPrincipal>; if (user != null) return (user.IsInRole(Roles) || user.IsInUser(Users)); return false; } protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext) { //验证不通过,直接跳转到相应页面,注意:如果不使用以下跳转,则会继续执行Action方法 filterContext.Result = new RedirectResult("http://www.baidu.com"); } }
好了,四个类定义完成,接下来是使用
1、首先是登陆
[HttpPost] public ActionResult LogOn(LogOnModel model, string returnUrl) { if (ModelState.IsValid) { //通过数据库查询验证 var bll= new UserBll(); var userId = bll.Validate(model.UserName, model.Password, HttpContext.Request.UserHostAddress, HttpContext.Request.UserAgent); if (userId > 0) { //验证成功,用户名密码正确,构造用户数据(可以添加更多数据,这里只保存用户Id) var userData = new MyUserDataPrincipal {UserId = userId}; //保存Cookie MyFormsAuthentication<MyUserDataPrincipal>.SetAuthCookie(model.UserName, userData, model.RememberMe); if (Url.IsLocalUrl(returnUrl) && returnUrl.Length > 1 && returnUrl.StartsWith("/") && !returnUrl.StartsWith("//") && !returnUrl.StartsWith("/\")) { return Redirect(returnUrl); } else { return RedirectToAction("Index", "Home"); } } else { ModelState.AddModelError("", "提供的用户名或密码不正确。"); } } // 如果我们进行到这一步时某个地方出错,则重新显示表单 return View(model); }
二、登陆完成后,是验证,验证之前首先要获得客户端的用户数据(从之前设置的Cookie中解析)
在全局文件:Global.asax 中添加下面代码
protected void Application_PostAuthenticateRequest(object sender, System.EventArgs e) { var formsIdentity = HttpContext.Current.User.Identity as FormsIdentity; if (formsIdentity != null && formsIdentity.IsAuthenticated && formsIdentity.AuthenticationType == "Forms") { HttpContext.Current.User = MyFormsAuthentication<MyUserDataPrincipal>.TryParsePrincipal(HttpContext.Current.Request); } }
这样就从Request解析出了UserData,下面可以用于验证了
三、在需要验证角色的Action上添加 [MyAuthorize] 特性
[MyAuthorize(Roles = "User", Users = "bomo,toroto")] public ActionResult About() { return View(); }
当用户访问该Action时,调用 MyAuthorize 的 AuthorizeCore 方法进行验证, 如果验证成功,则继续执行,如果验证失败,会调用 HandleUnauthorizedRequest方法做相应处理,在MyAuthorize 中可以获得这里定义的 Roles 和 Users 进行验证