MVC 登录认证与授权及读取登录错误码

时间:2023-03-09 07:38:47
MVC 登录认证与授权及读取登录错误码

最近悟出来一个道理,在这儿分享给大家:学历代表你的过去,能力代表你的现在,学习代表你的将来。

十年河东十年河西,莫欺少年穷

学无止境,精益求精

   最近在自学MVC,遇到的问题很多,索性一点点总结下。

[AllowAnonymous] 用于无需授权登录的控制器

MVC 登录认证与授权及读取登录错误码

正题:

做过三层架构的童鞋都知道,如果要验证/授权一个用户登录,我们一般采取如下方法:

1、用户输入用户名、密码进行登录

2、账户/密码正确,保存用户信息至Cookies或者Session,跳转至主页面

3、在主页面继承BasePage类,并在Page_Load的时候,判断Cookies或者Session的值,如果Cookies或者Session中存放的值在数据库验证成功,则可以观看主页面信息,如果验证不成功,或者Session/Cookies值为NULL时,直接执行语句:Response.Redirect(../Login.aspx);

上述方法很简单,功能上也实现了登录的认证与授权,相信很多人写的项目都会采取此方法进行判断,殊不知:这种方法安全系数较低,而且违背了NET的登录授权与验证原则。那么NET中的授权与验证又是什么呢?

.net中的认证(authentication)与授权(authorization)

   认证(authentication) 就是 :判断用户有没有登录,用户输入的账户密码等信息是否通过数据库比对

授权(authorization) 就是:用户登录后的身份/角色识别,用户通过认证后<登陆后>我们记录用户的信息并授权

.net中与"认证"对应的是IIdentity接口,而与"授权"对应的则是IPrincipal接口,这二个接口的定义均在命名空间System.Security.Principal中,详情如下:

用户认证接口:

using System;
using System.Runtime.InteropServices; namespace System.Security.Principal
{
[ComVisible(true)]
public interface IIdentity
{
string AuthenticationType { get; }
bool IsAuthenticated { get; }
string Name { get; }
}
}

用户授权接口:

using System;
using System.Runtime.InteropServices; namespace System.Security.Principal
{
[ComVisible(true)]
public interface IPrincipal
{
IIdentity Identity { get; }
bool IsInRole(string role);
}
}

应该注意到:IPrincipal接口中包含着一个只读的IIdentity

1、IIdentity 接口中属性的含义:

AuthenticationType  验证方式:NET中有Windows、Forms、Passport三种验证方式,其中以Forms验证用的最多,本节讲解的MVC验证与授权就是基于Form。

IsAuthenticated  是否通过验证,布尔值

Name  用户登录的账户名称

2、IPrincipal 接口中属性的含义:

IIdentity  上述IIdentity接口的实例

IsInRole  布尔值,验证用户权限

下面我们对HttpContext封闭类刨根问底

MVC 登录认证与授权及读取登录错误码

HttpContext.User本身就是一个IPrincipal接口的实例。

有了上面的预备知识,我们在程序中处理认证问题时,只需对HttpContext.User赋值即可。

下面是小弟模拟的一个授权方法

        /// <summary>
/// 模拟授权 此处不经过验证 直接授权访问 UserCenter()
/// </summary>
/// <returns></returns>
public ActionResult Login()
{
GenericIdentity _identity = new GenericIdentity("天才卧龙", "Forms");
GenericPrincipal _principal = new GenericPrincipal(_identity, new string[] { "admins", "vips" });
HttpContext.User = _principal;
return RedirectToAction("UserCenter");
} [Authorize(Roles = "admins")]
public ActionResult UserCenter()
{
return View();
}

上面的方法中,一旦运行登陆页,不需要填账户密码,直接会访问UserCenter(),即:直接会跳转到个人中心

好啦,如果上边的例子大家看懂了,那么下面的登录其实就很简单了,和上述所讲一样:由于 HttpContext.User 本身就是一个IPrincipal接口的实例!因此:我们只需给这个实例赋值即可,上面的例子就证实了这一点。

咱们回到开篇的描述,一般我们都是通过Session或者cookies就行Forms验证登录,那么在MVC中我们应当怎么做呢?

   1.MVC的Forms验证

Forms验证在内部的机制为把用户数据加密后保存在一个基于cookie的票据FormsAuthenticationTicket中,因为是经过特殊加密的,所以应该来说是比较安全的。而.net除了用这个票据存放自己的信息外,还留了一个地给用户*支配,这就是现在要说的UserData。

UserData可以用来存储string类型的信息,并且也享受Forms验证提供的加密保护,当我们需要这些信息时,也可以通过简单的get方法得到,兼顾了安全性和易用性,用来保存一些必须的敏感信息还是很有用的。

在此,本案例中UserData用于存储用户角色值,admins/vips/superadmins等,这些值大家可根据系统需求自行定义。本案例中是写死的,当然,在实际项目中应当根据登录账户信息结合数据库进行赋值。

  2、FormsAuthenticationTicket基于forms的验证

构建基于forms的验证机制过程如下: 
 1,设置IIS为可匿名访问和asp.net web.config中设置为form验证,配置webConfig,如下:

    <!--用户没有授权时 自动退回登陆界面-->
<authentication mode="Forms">
<forms loginUrl="~/Home/Login" timeout="" path="/" />
</authentication>

 2,检索数据存储验证用户,并检索角色(其实就是登录验证) 
 3,使用FormsAuthenticationTicket创建一个Cookie并回发到客户端,并存储

关于2、3两步,我以代码示例:

我创建的登录Model如下:

public class UserLogin
{
private readonly object LOCK = new object();
Maticsoft.BLL.YX_UserInfo Bll = new Maticsoft.BLL.YX_UserInfo();
Maticsoft.Model.YX_UserInfo Mol = new Maticsoft.Model.YX_UserInfo();
//
[Required(ErrorMessage = "请输入账户号码/手机号")]
[RegularExpression(@"^1[3458][0-9]{9}$", ErrorMessage = "手机号格式不正确")]
public string UserName { get; set; } [Required(ErrorMessage="请输入账户密码")]
[DataType(DataType.Password,ErrorMessage="密码格式不正确")]
[MinLength(, ErrorMessage = "密码长度介于6~15位之间")]
[MaxLength(, ErrorMessage = "密码长度介于6~15位之间")]
public string UserPwd { get; set; } public bool LoginAction()
{
lock (LOCK)
{
DataTable dt = Bll.GetList(" Uname='" + CommonMethod.CheckParamThrow(UserName) + "' and Upwd='" + CommonMethod.Md532(UserPwd) + "' and flat1=1").Tables[];
if (dt.Rows.Count > )
{
FormsAuthenticationTicket authTicket = new FormsAuthenticationTicket(
,
UserName,
DateTime.Now,
DateTime.Now.AddMinutes(),
false,
"admins,vip",
"/"
);
string encryptedTicket = FormsAuthentication.Encrypt(authTicket);
System.Web.HttpCookie authCookie = new System.Web.HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket);
System.Web.HttpContext.Current.Response.Cookies.Add(authCookie);
return true;
}
else
{
return false;
}
}
}
}

上述代码中

MVC 登录认证与授权及读取登录错误码

数据库验证成功,我们将用户信息存入Cookies,

然后我们在Global中解析这个Cookies,然后把解析的结果赋值给:HttpContext.User

代码如下:

        /// <summary>
/// 登录验证、s授权
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
protected void Application_AuthenticateRequest(Object sender, EventArgs e)
{
string cookieName = FormsAuthentication.FormsCookieName;
HttpCookie authCookie = Context.Request.Cookies[cookieName];
FormsAuthenticationTicket authTicket = null;
try
{
authTicket = FormsAuthentication.Decrypt(authCookie.Value);
}
catch (Exception ex)
{
return;
}
string[] roles = authTicket.UserData.Split(',');
FormsIdentity id = new FormsIdentity(authTicket);
GenericPrincipal principal = new GenericPrincipal(id, roles);
Context.User = principal;//存到HttpContext.User中    
}

Context.User一旦被赋值成功,我们就可以访问那些有权限限制的方法。代码如下:

        [Authorize(Roles = "admins")]
public ActionResult UserCenter()
{
return View();
}

SO,搞懂了本质,一切都可以解决。

补充:本人登录代码如下:

前端 Login.cshtml

@{
ViewBag.Title = "登录";
}
@section css
{
<link href="~/Content/login.css" rel="stylesheet" />
}
<form id="form1">
<div class="wrap loginBox">
<div class="logo"><img src="/images/logo.png"/></div>
<div class="head">
<ul>
<li><input type="text" id="UserName" name="UserName" class="text n2" placeholder="手机号码" /></li>
<li><input type="password" id="UserPwd" name="UserPwd" class="text n3" placeholder="密码" /></li>
</ul>
</div>
<div class="body">
<a href="JavaScript:void(0)" class="btn btn-blue" onclick="Login()">立即登录</a>
@*<a href="JavaScript:void(0)" class="btn btn-yell">逗包账号登录</a>*@
</div>
<div class="foot">
<a href="JavaScript:void(0)">忘记密码?</a><a href="JavaScript:void(0)">账号注册</a>
</div>
</div>
</form>
<script type="text/javascript">
function Login() {
var UserName = $("#UserName").val();
var UserPwd = $("#UserPwd").val();
$(document).ready(function (data) {
$.ajax({
url: "/home/Login",
type: "post",
contentType: "application/json",
dataType: "text",
data: JSON.stringify({ UserName: UserName, UserPwd: UserPwd }),
success: function (result, status) {
if (result == "") {
//登录成功 跳转
location.href = "http://www.baidu.com";
}
else {
alert(result);//弹出错误码
}
},
error: function (error) {
alert(error);
}
});
});
}
</script>

后端 HomeController.cs:

    public class HomeController : Controller
{
public ActionResult Index()
{
return RedirectToAction("Login");
} public ActionResult Login()
{
return View();
} [HttpPost]
public object Login(UserLogin LoginMol)
{
if (NetworkHelper.Ping())//判断当前是否有网络
{
if (ModelState.IsValid)//是否通过Model验证
{
if (LoginMol.LoginAction())
{
return ;
}
else
{
return "账户或密码有误";
}
}
else
{
//读取错误信息
StringBuilder sb = new StringBuilder("");
var errors = ModelState.Values;
foreach (var item in errors)
{
foreach (var item2 in item.Errors)
{
if (!string.IsNullOrEmpty(item2.ErrorMessage))
{
if (string.IsNullOrEmpty(sb.ToString()))
{
sb.AppendLine(item2.ErrorMessage);//读取一个错误信息 并返回
}
}
}
}
return sb.ToString();
}
}
else
{
return "当前无网络,请稍后重试";
}
}
}

上述既是完整登录代码,谢谢!

@陈卧龙的博客