一、前言
关于表单验证,已经有不少的文章,相信Web开发人员也都基本写过,最近在一个个人项目中刚好用到,在这里与大家分享一下。本来想从用户注册开始写起,但发现东西比较多,涉及到界面、前端验证、前端加密、后台解密、用户密码Hash、权限验证等等,文章写起来可能会很长,所以这里主要介绍的是登录验证和权限控制部分,有兴趣的朋友欢迎一起交流。
一般验证方式有Windows验证和表单验证,web项目用得更多的是表单验证。原理很简单,简单地说就是利用浏览器的cookie,将验证令牌存储在客户端浏览器上,cookie每次会随请求发送到服务器,服务器验证这个令牌。通常一个系统的用户会分为多种角色:匿名用户、普通用户和管理员;这里面又可以再细分,例如用户可以是普通用户或Vip用户,管理员可以是普通管理员或超级管理员等。在项目中,我们有的页面可能只允许管理员查看,有的只允许登录用户查看,这就是角色区分(Roles);某些特别情况下,有些页面可能只允许叫“张三”名字的人查看,这就是用户区分(Users)。
我们先看一下最后要实现的效果:
1.这是在Action级别的控制。
public class Home1Controller : Controller { //匿名访问 public ActionResult Index() { return View(); } //登录用户访问 [RequestAuthorize] public ActionResult Index2() { return View(); } //登录用户,张三才能访问 [RequestAuthorize(Users="张三")] public ActionResult Index3() { return View(); } //管理员访问 [RequestAuthorize(Roles="Admin")] public ActionResult Index4() { return View(); } }</div>
2.这是在Controller级别的控制。当然,如果某个Action需要匿名访问,也是允许的,因为控制级别上,Action优先级大于Controller。
//Controller级别的权限控制 [RequestAuthorize(User="张三")] public class Home2Controller : Controller { //登录用户访问 public ActionResult Index() { return View(); } //允许匿名访问 [AllowAnonymous] public ActionResult Index2() { return View(); } }</div>
3.Area级别的控制。有时候我们会把一些模块做成分区,当然这里也可以在Area的Controller和Action进行标记。
从上面可以看到,我们需要在各个地方进行标记权限,如果把Roles和Users硬写在程序中,不是很好的做法。我希望能更简单一点,在配置文件进行说明。例如如下配置:
<?xml version="1.0" encoding="utf-8" ?> <!-- 1.这里可以把权限控制转移到配置文件,这样就不用在程序中写roles和users了 2.如果程序也写了,那么将覆盖配置文件的。 3.action级别的优先级 > controller级别 > Area级别 --> <root> <!--area级别--> <area name="Admin"> <roles>Admin</roles> </area> <!--controller级别--> <controller name="Home2"> <user>张三</user> </controller> <!--action级别--> <controller name="Home1"> <action name="Inde3"> <users>张三</users> </action> <action name="Index4"> <roles>Admin</roles> </action> </controller> </root></div>
写在配置文件里,是为了方便管理,如果程序里也写了,将覆盖配置文件的。ok,下面进入正题。
二、主要接口
先看两个主要用到的接口。
IPrincipal 定义了用户对象的基本功能,接口定义如下:
public interface IPrincipal { //标识对象 IIdentity Identity { get; } //判断当前角色是否属于指定的角色 bool IsInRole(string role); }</div>
它有两个主要成员,IsInRole用于判断当前对象是否属于指定角色的,IIdentity定义了标识对象信息。HttpContext的User属性就是IPrincipal类型的。
IIdentity 定义了标识对象的基本功能,接口定义如下:
public interface IIdentity { //身份验证类型 string AuthenticationType { get; } //是否验证通过 bool IsAuthenticated { get; } //用户名 string Name { get; } }</div>
IIdentity包含了一些用户信息,但有时候我们需要存储更多信息,例如用户ID、用户角色等,这些信息会被序列到cookie中加密保存,验证通过时可以解码再反序列化获得,状态得以保存。例如定义一个UserData。
public class UserData : IUserData { public long UserID { get; set; } public string UserName { get; set; } public string UserRole { get; set; } public bool IsInRole(string role) { if (string.IsNullOrEmpty(role)) { return true; } return role.Split(',').Any(item => item.Equals(this.UserRole, StringComparison.OrdinalIgnoreCase)); } public bool IsInUser(string user) { if (string.IsNullOrEmpty(user)) { return true; } return user.Split(',').Any(item => item.Equals(this.UserName, StringComparison.OrdinalIgnoreCase)); } }</div>
UserData实现了IUserData接口,该接口定义了两个方法:IsInRole和IsInUser,分别用于判断当前用户角色和用户名是否符合要求。该接口定义如下:
public interface IUserData { bool IsInRole(string role); bool IsInUser(string user); } 接下来定义一个Principal实现IPrincipal接口,如下: public class Principal : IPrincipal { public IIdentity Identity{get;private set;} public IUserData UserData{get;set;} public Principal(FormsAuthenticationTicket ticket, IUserData userData) { EnsureHelper.EnsureNotNull(ticket, "ticket"); EnsureHelper.EnsureNotNull(userData, "userData"); this.Identity = new FormsIdentity(ticket); this.UserData = userData; } public bool IsInRole(string role) { return this.UserData.IsInRole(role); } public bool IsInUser(string user) { return this.UserData.IsInUser(user); } }</div>
Principal包含IUserData,而不是具体的UserData,这样很容易更换一个UserData而不影响其它代码。Principal的IsInRole和IsInUser间接调用了IUserData的同名方法。
三、写入cookie和读取cookie
接下来,需要做的就是用户登录成功后,创建UserData,序列化,再利用FormsAuthentication加密,写到cookie中;而请求到来时,需要尝试将cookie解密并反序列化。如下:
public class HttpFormsAuthentication { public static void SetAuthenticationCookie(string userName, IUserData userData, double rememberDays = 0) { EnsureHelper.EnsureNotNullOrEmpty(userName, "userName"); EnsureHelper.EnsureNotNull(userData, "userData"); EnsureHelper.EnsureRange(rememberDays, "rememberDays", 0); //保存在cookie中的信息 string userJson = JsonConvert.SerializeObject(userData); //创建用户票据 double tickekDays = rememberDays == 0 ? 7 : rememberDays; var ticket = new FormsAuthenticationTicket(2, userName, DateTime.Now, DateTime.Now.AddDays(tickekDays), false, userJson); //FormsAuthentication提供web forms身份验证服务 //加密 string encryptValue = FormsAuthentication.Encrypt(ticket); //创建cookie HttpCookie cookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptValue); cookie.HttpOnly = true; cookie.Domain = FormsAuthentication.CookieDomain; if (rememberDays > 0) { cookie.Expires = DateTime.Now.AddDays(rememberDays); } HttpContext.Current.Response.Cookies.Remove(cookie.Name); HttpContext.Current.Response.Cookies.Add(cookie); } public static Principal TryParsePrincipal<TUserData>(HttpContext context) where TUserData : IUserData { EnsureHelper.EnsureNotNull(context, "context"); HttpRequest request = context.Request; HttpCookie cookie = request.Cookies[FormsAuthentication.FormsCookieName
您可能想查找下面的文章:
- 详解ASP.NET MVC 常用扩展点:过滤器、模型绑定
- ASP.NET MVC从视图传参到控制器的几种形式
- ASP.NET MVC 4 中的JSON数据交互的方法
- ASP.NET MVC制作404跳转实例(非302和200)
- 详解ASP.NET MVC 利用Razor引擎生成静态页
- 详解ASP.NET MVC 解析模板生成静态页(RazorEngine)
- ASP.NET MVC4 利用uploadify.js多文件上传
- ASP.NET mvc4中的过滤器的使用
- Asp.net MVC下使用Bundle合并、压缩js与css文件详解
- 详解Asp.Net MVC——控制器与动作(Controller And Action)