前言
认证和授权机制
认证是确定用户身份的过程。在用户通过了身份验证后,开发人员
就可以确定该用户是否有权继续操作。如果没有进行身份验证,就
不能进行实体的授权。
授权是确定已验证的用户是否有权访问应用程序中的某个部分、某
个点、或只访问应用程序提供的特定数据集。
今天分享关于.NET MVC中的Forms窗体登录验证,及ActionFilterAttribute角色拦截。登录方式可以为二种方式。
第一种为:form表单提交。
第二种:Ajax异步提交。好话不多说,直接上代码。
登录概要流程图
Web.Config配置
(1)在web.Config配置中的 system.web 节点内添加authentication认证节点。
(2)Mode : ASP.NET支持3种授权方法
●Windows : IIS验证。在内联网环境中非常有用。
●Passport :微软集中式身份验证, -次登录便可访问所有成员
站点,需要收费。
●Forms:窗体验证,验证帐号/密码, Web编程最佳流行的验证方
式。
那这里我们选择Forms窗体验证。
(3)关于Forms节点中所用到的属性:
●name:是赋予Cookie的名字 用于在请求之间保存用户,默认值为.Aspxauth
●loginurl:需要处理验证的页面。我们这里是登录页面处理。如果没有找到有效的验证cookie 就指定重新定西的URL
●timeout:指定Cookie过期的时间(分钟) ,这里指定过期时间为20分钟。
<system.web>
<authentication mode="Forms">
<forms name="auth" loginUrl="/Account/Login" timeout="20" > </forms>
</authentication>
</system.web>
登录视图
(1)在视图中表单需要注意的是 action 提交地址。另外在控制器中,如果指定了提交方式,那一定注意method 的提交方式。且应该使用post。还有就是 用户框与密码框的name属性值。传递参数的命名为相关name值。
<form class="layui-form" id="form" action="/Account/Login" method="post">
<label class="layui-form-label" >用户名</label>
<input type="text" name="LoginId" required lay-verify="required" placeholder="请输入用户名" autocomplete="off" >
<label class="layui-form-label">密码</label>
<input type="password" name="LoginPWD" required lay-verify="required" placeholder="请输入密码" autocomplete="off" >
@*方式一,表单提交*@
<button class="layui-btn lg_btn" type="submit" lay-submit lay-filter="formDemo">立即登录</button>
</form>
登录控制器
(1)通过视图用户输入的用户名以及密码,向数据库中用户表去查询并返回个人信息的实体类。
(2)判断个人的状态 status 是否 离职
(3)在登录密码成功且个人状态在职的情况下,开始创建票据。FormsAuthenticationTicket。参数为:版本号,用户名,票据创建日期,票据过期时间,是否持续票据,用户自定义数据 。
(4)在票据 FormsAuthenticationTicket 中的UserData只能放字符串类型,要是我们想把用户对象存储进去的话。可以将对象序列化成json格式字符串。要想使用的话,反序列化就好了。
(5) 对新建的票据 进行 加密票据 使用的方法 FormsAuthentication. Encrypt。 利用票据生成Cookie然后保存到Cookies。注意 Cookie的Key 指的是窗体验证的名字 FormsAuthentication.FormsCookieName
(5)跳转页面,这里为员工index页面。
[HttpPost]
public ActionResult Login(string LoginId, string LoginPWD)
{
//MD5加密
LoginPWD = Converters.ToolMD5Encrypt(LoginPWD);
employee employee = new employee();
employee.Login_name = LoginId;
employee.Login_pwd = LoginPWD;
if (ES.LoginVilidate(employee) != null)
{
//通过输入的用户名和密码进行数据库匹配并返回实体信息
employee SuccesEntity = ES.LoginVilidate(employee);
if (SuccesEntity.Status == "1")
{
ViewBag.LoginMesssage = "用户不存在";
}
else
{
//创建一个授权对象类 存储相关信息
Authinfor AuthEntity = new Authinfor {
Emp_id = SuccesEntity.Emp_id, Name = SuccesEntity.Name, Roles_name = SuccesEntity.Roles_name };
//序列化授权对象类,用于放入票据中的用户自定义事件
string userDataJson = JsonConvert.SerializeObject(AuthEntity);
FormsAuthenticationTicket Tick = new FormsAuthenticationTicket(
1, //版本号
SuccesEntity.Name, //用户名
DateTime.Now, //创建票据日期
DateTime.Now.AddMinutes(FormsAuthentication.Timeout.TotalMinutes), //票据持续时间
false, //是否持久性
userDataJson); //用户自定义存储数据
string eny = FormsAuthentication.Encrypt(Tick); //对创建好了的票据进行加密
//将票据保存在
HttpCookie HK = new HttpCookie(FormsAuthentication.FormsCookieName, eny);
Response.Cookies.Add(HK);
return Redirect("/Employees/index");
}
}
else
{
ViewBag.LoginMesssage = "登录失败";
}
return View();
}
注意:我这里因为注册员工的时候,密码采用的是MD5加密存储在数据库中。所以在判断员工输入密码是否正确的时候,需要再次MD5加密两边统一格式,然后在数据库中查询相关信息。
/// <summary>
/// MD5加密
/// </summary>
/// <param name="value">要加密的字符串</param>
/// <returns>加密好的字符串</returns>
public static string ToolMD5Encrypt(string value)
{
try
{
MD5 md5 = new MD5CryptoServiceProvider();
byte[] btSource = System.Text.Encoding.Default.GetBytes(value);
byte[] btResult = md5.ComputeHash(btSource);
string encryptData = "";
for (int i = 0; i < btResult.Length; i++)
{
encryptData += btResult[i].ToString("X");
}
return encryptData;
}
catch (Exception ex)
{
throw ex;
}
}
那么以上就完成了窗体验证的流程了。接下来需要注意的一点是。要对哪一个视图页面进行需要验证才能访问呢?加入 [Authorize] 标志即可。
(1)登录控制器中,我是验证成功之后重定向地址为员工的index页面。所以我在 员工index控制器上面加个认证的标志 .
// GET: Employees
[CheckFilter("经理")]
[Authorize]
public ActionResult Index()
{
return View();
}
(2)接下来将要完成角色拦截了。也就是说,不是每个页面,任何员工都能访问的。
(3)新建一个类,类名自定义,我这边为 CheckFilter。
(4)自定义拦截器,继承继承ActionFilterAttribute抽象类,重写其中需要的方法,来看下ActionFilterAttribute类的方法签并重写。
●OnActionExecuting:将要进入Action 时要处理的虚方法,这里可以对接收到的Request请求进行处理或拦截。
●OnActionExecuted:已进入Action 了,这里可以继续处理点事情或拦截
●OnResultExecuting:响应Result之前,这里可以继续处理点事情或拦截
●OnResultExecuted:响应Result完成之后,这里可以继续处理点事情或拦截
自定义拦截器类
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Security;
using Newtonsoft.Json;
using Model.BusinessMsage;
namespace TestHoses.Code
{
public class CheckFilter : ActionFilterAttribute
{
protected string Roles {
get; set; }
protected bool IsAjaxs {
get; set; }
/// <summary>
/// 构造函数
/// </summary>
/// <param name="roles"></param>
/// <param name="ajaxs"></param>
public CheckFilter(string roles="", bool ajaxs = false)
{
Roles = roles;
IsAjaxs = ajaxs;
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
//判断当前请求类型是否为Ajax请求
if (filterContext.HttpContext.Request.IsAjaxRequest())
{
this.IsAjaxs = true;
}
}
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
//获取当前验证成功票据中的用户自定义数据
string userData = ((FormsIdentity)filterContext.HttpContext.User.Identity).Ticket.UserData;
//反序列化获取对象
Authinfor authinfor = JsonConvert.DeserializeObject<Authinfor>(userData);
if (!authinfor.Roles_name.Split(',').Contains(Roles))
{
if (!IsAjaxs)
{
filterContext.HttpContext.Response.Redirect("/Home/Error");
}
else
{
filterContext.HttpContext.Response.Write("{\"meeage:\"你没权限\"}");
filterContext.HttpContext.Response.End();
}
}
else {
if (IsAjaxs) {
filterContext.HttpContext.Response.Write("{\"meeage:\"true\"}");
filterContext.HttpContext.Response.End();
}
}
}
}
}
(1)封装字段属性,角色名,是否为ajax请求。
(2)在Action执行之前,对Request.IsAjaxRequest() 判断,判断当前是否为AJax请求。
(3)在Action执行之后,首先获取当前授权信息。也就是在票据中存储的userData。在存储之前,我们对他进行了序列化,所以读取的时候需要反序列化回来。
(4)判断该用户角色是否合法,是否为Ajax请求。
(5)那为什么这里要判断是否为ajax请求呢?因为我们要进行拦截处理的时候,我们需要重定向到其他页面地址,并告诉该用户没有权限访问。而Ajax属于局部刷新,Response.Redirect()重定向地址对它无效。因此我们需要判断请求类型,根据请求类型来处理一些事情。如果是Ajax请求,那么就返回指定信息或状态,在前端js中依据指定信息或状态来处理。非Ajax请求的话,就直接重定向相关页面地址,给出提示。