MVC 中的登录功能






4.88/5 (27投票s)
使用表单身份验证在 MVC 中实现自定义登录功能。
引言
让我们首先确定代码的目的是什么。
本文代码的目的是使用表单身份验证在 MVC 中创建登录和注销功能。我们还将讨论使用哈希(Hashing)将密码存储在数据库中的最佳方法。
背景
最重要的问题是密码如何受到保护。如果您以纯文本形式存储密码,或使用加密/解密(双向)则是一个糟糕的主意。如果您以加密格式存储密码,则有可能使用加密输出恢复到纯文本值。
这是将密码存储在数据库中的最佳解决方案。我们使用单向哈希算法加密密码。首先,我们使用 SHA512 算法(bcrypt/PBKDF2/scrypt 也是最好的哈希算法)创建密码、一个唯一字段(例如用户名、手机号或电子邮件)和盐(SALT)组合的哈希值,并使用 CSPRNG 创建一个唯一的盐。然后我们将哈希值和盐存储在数据库中。
我们不需要知道密码,只需验证输入的密码。因此,当用户尝试登录时,我们会将用户输入的密码和唯一字段(用户输入的)以及盐组合创建一个哈希值,然后与从数据库检索到的真实密码的哈希值进行比较。如果哈希值匹配,用户将被授予访问权限。否则,将告知用户输入的登录凭据无效。
Using the Code
首先,我们需要创建一个包含用户信息的数据库和数据表。在此开始编写代码。
步骤 01:创建一个名为“DemoLoginFunctionality”的数据库。
Script:
CREATE DATABASE DemoLoginFunctionality;
以下脚本用于创建包含数据条目(Data Entries)的数据表。
USE [DemoLoginFunctionality]
GO
/****** Object: Table [dbo].[UserMaster] Script Date: 15-07-2016 11:30:49 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
SET ANSI_PADDING ON
GO
CREATE TABLE [dbo].[UserMaster](
[UserID] [bigint] IDENTITY(1,1) NOT NULL,
[Username] [nvarchar](50) NOT NULL,
[HASH] [nvarchar](max) NOT NULL,
[SALT] [varbinary](512) NOT NULL,
CONSTRAINT [PK_UserMaster] PRIMARY KEY CLUSTERED
(
[UserID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO
SET ANSI_PADDING OFF
GO
SET IDENTITY_INSERT [dbo].[UserMaster] ON
GO
INSERT [dbo].[UserMaster] ([UserID], [Username], [HASH], [SALT]) VALUES (1, N'suchit', N'duD96EJIW3AhCKE9vcl8aev1M2yULlLS4dHqqyFISrpvAdjNvNlVjKHC2Xsi4wsDyHRWa/wxeGmNqd37nQdgS+IhX/AsWEpPklL2LlBMFxsXiviznHWOMb/OjcTDWhM6', 0xE2215FF02C584A4F9252F62E504C171B178AF8B39C758E31BFCE8DC4C35A133A)
GO
SET IDENTITY_INSERT [dbo].[UserMaster] OFF
GO
注意:我们将密码的哈希值和盐存储在数据库中。
--- 注册时(生成哈希值和盐) ---
1) 首先,我们需要创建盐。我们使用CSPRNG(加密安全的伪随机数生成器)来创建盐。
什么是盐(SALT)?
盐是随机数据,用作单向函数(对密码进行“哈希”)的附加输入。
什么是 CSPRNG?
加密安全的伪随机数生成器(CSPRNG)是一种伪随机数生成器(PRNG),其特性使其适合在密码学中使用。它使用数学公式生成随机数序列。
#region --> Generate SALT Key private static byte[] Get_SALT() { return Get_SALT(saltLengthLimit); } private static byte[] Get_SALT(int maximumSaltLength) { var salt = new byte[maximumSaltLength]; //Require NameSpace: using System.Security.Cryptography; using (var random = new RNGCryptoServiceProvider()) { random.GetNonZeroBytes(salt); } return salt; } #endregion
2) 接下来,第二步是创建哈希值。我们使用SHA512来创建哈希值。
什么是密码哈希(password hashing)?
哈希对密码执行单向转换,将密码转换为另一个字符串,称为哈希密码。“单向”意味着几乎不可能反向操作——将哈希密码还原为原始密码。它们还具有一个特性,即如果输入发生微小变化,生成的哈希值也会完全不同。
i.e. hashSHA512("hello") = ekFc4y+0WZ/jHte2PNzvSLC/WLKel86uRb71r6JliSrgjEkKeFQkTL2Ltix+1mcybYsJZuaAp7x7dMUi+1sQCmzR/JoMziQU/XKv0YK92oYMywLb9QJYNYiZDITZNlNu hashSHA512("hollo") = HntRl+7m3KQ6fDNhqGPYDz3tCAan9gtVL3ad/Z6Qm5aQ3bzDUXUBy+mfgmSVmzfQBYV+3K8A9pWwcRFJxpNYwwVKsB4HudvUsCGPE0JiDx+nX9ot/UU7fn5V1jOTos1g hashSHA512("helle") = UXaSsPL0G0sY/jqccVMrjazoFgpeDihMu5xwSo3AYWqpHuozHee9zfRviHL1SF+6E/0nU/aV/rM+LLjbTUfBpNI4nPKFwCTmRYmvMrT2npHHsBaIO/w8cpIsJ7y5WiNv
什么是 SHA512?
安全哈希算法(SHA512)是由美国国家安全局(NSA)设计的一组加密哈希函数。
//CODE: Generate HASH Using SHA512 public static string Get_HASH_SHA512(string password, string username, byte[] salt) { try { //required NameSpace: using System.Text; //Plain Text in Byte byte[] plainTextBytes = Encoding.UTF8.GetBytes(password + username); //Plain Text + SALT Key in Byte byte[] plainTextWithSaltBytes = new byte[plainTextBytes.Length + salt.Length]; for (int i = 0; i < plainTextBytes.Length; i++) { plainTextWithSaltBytes[i] = plainTextBytes[i]; } for (int i = 0; i < salt.Length; i++) { plainTextWithSaltBytes[plainTextBytes.Length + i] = salt[i]; } HashAlgorithm hash = new SHA512Managed(); byte[] hashBytes = hash.ComputeHash(plainTextWithSaltBytes); byte[] hashWithSaltBytes = new byte[hashBytes.Length + salt.Length]; for (int i = 0; i < hashBytes.Length; i++) { hashWithSaltBytes[i] = hashBytes[i]; } for (int i = 0; i < salt.Length; i++) { hashWithSaltBytes[hashBytes.Length + i] = salt[i]; } return Convert.ToBase64String(hashWithSaltBytes); } catch { return string.Empty; } }
现在,将哈希值和盐存储在数据库中。
步骤 02:创建新的 MVC 应用程序项目。
1) 在“文件”菜单上,单击“新建项目”。
2) 在“新建项目”对话框中,在“项目类型”下展开“Visual C#”,然后单击“Web”,并在“名称”框中键入“LoginFunctionalityMVC”,然后单击“确定”。
3) 现在,在对话框中,在 ASP.NET 4.5.2 模板下单击“MVC”,然后单击位于右侧中间的“更改身份验证”。
步骤 03:现在,新的 MVC 应用程序已创建。接下来,我们需要创建一个 EDMX 并将我们的数据库“DemoLogin”与 EDMX 绑定。
1) 在右侧,您可以找到“解决方案资源管理器”。
2) 在“解决方案资源管理器”中,右键单击“Models”文件夹。然后单击“添加”,然后单击“新建项...”
3) 现在单击 Visual C# 并选择ADO.NET Entity Data Model,将其命名为“DBModel”,然后单击“确定”。
4) 选择“EF Designer from Database”,然后单击“下一步”。
5) 现在单击“新建连接”,然后定义“服务器名称”,选择身份验证模式,是 Windows 还是 SQL Server,如果是 SQL Server,则输入用户名或密码。最后,在“连接到数据库”下选择数据库“DemoLoginFunctionality”,然后单击“确定”。
6) 现在,在“将连接设置保存在 Web.config 中,名称为:”下将连接字符串命名为“DBEntities”,然后单击“下一步”。
7) 选择实体框架的版本。选择“Entity Framework 6.x”,然后单击“下一步”。
8) 展开“Table”,然后展开“dbo”,然后选择我们的数据表“UserMaster”。现在,在“模型命名空间”下将命名空间设置为“Models”,然后单击“确定”。
9) 现在,通过按CLTR + B构建您的项目,以完美更新所有实体。
步骤 04:添加一个空的控制器。
1) 要添加控制器,请右键单击“controller 文件夹”,选择“添加”,然后单击“控制器”。
2) 现在,在“添加脚手架对话框”中选择“MVC 5 Controller - Empty”,然后单击“添加”并将其命名为“HomeController”,然后单击“添加”。
创建一个名为“Login”的新 ActionResult
方法。
[HttpGet]
public ActionResult Login(string returnURL)
{
var userinfo = new LoginVM();
try
{
// We do not want to use any existing identity information
EnsureLoggedOut();
// Store the originating URL so we can attach it to a form field
userinfo.ReturnURL = returnURL;
return View(userinfo);
}
catch
{
throw;
}
}
//Create A New ViewModel Name AS "LoginVM" into Models Folders & Declare Below Properties.
public class LoginVM
{
public string Username { get; set; }
public string Password { get; set; }
public string ReturnURL { get; set; }
public bool isRemember { get; set; }
}
步骤 05:添加一个视图。
1) 为此,请右键单击您的“Login”操作方法,然后选择“添加视图”。
2) 现在,取消选中“使用布局页”,然后单击“添加”按钮。
3) 在视图顶部添加模型类。
@model LoginFunctionalityMVC.Models.LoginVM
4) 在 Index.cshtml(视图)的 form 标签中,添加一个电子邮件、密码文本框、复选框(用于“记住我”选项)和提交按钮。
//Paste below code in your view
@model LoginFunctionalityMVC.Models.LoginVM
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Login</title>
</head>
<body>
@using (Html.BeginForm("Login", "Home", FormMethod.Post))
{
@Html.AntiForgeryToken()
@Html.HiddenFor(s => s.ReturnURL)
<h1>Login Functionality In MVC</h1>
<div>
@if (TempData["ErrorMSG"] != null)
{
<label style="color:maroon;"> @TempData["ErrorMSG"] </label>
<br /><br />
}
@Html.TextBoxFor(s => s.Username, new { @placeholder = "Username" })
<br /> <br />
@Html.PasswordFor(s => s.Password, new { @placeholder = "Password" })
<br /> <br />
@Html.CheckBoxFor(s => s.isRemember) Remember ME
<br /> <br />
<button type="submit">Login</button>
</div>
}
</body>
</html>
注意:别忘了在视图中声明 AntiForgeryToken。
AntiForgeryToken
: 防伪令牌可用于帮助保护您的应用程序免受跨站请求伪造(CSRF)的侵害。
步骤 06:当登录页面初始化时,此时我们需要检查当前会话是否已注销。因此,我们首先注销现有用户。
为此,我们创建两个方法:“EnsureLoggedOut
”和“Logout
”。
//GET: EnsureLoggedOut
private void EnsureLoggedOut()
{
// If the request is (still) marked as authenticated we send the user to the logout action
if (Request.IsAuthenticated)
Logout();
}
//POST: Logout
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Logout()
{
try
{
// First we clean the authentication ticket like always
//required NameSpace: using System.Web.Security;
FormsAuthentication.SignOut();
// Second we clear the principal to ensure the user does not retain any authentication
//required NameSpace: using System.Security.Principal;
HttpContext.User = new GenericPrincipal(new GenericIdentity(string.Empty), null);
Session.Clear();
System.Web.HttpContext.Current.Session.RemoveAll();
// Last we redirect to a controller/action that requires authentication to ensure a redirect takes place
// this clears the Request.IsAuthenticated flag since this triggers a new request
return RedirectToLocal();
}
catch
{
throw;
}
}
注意:别忘了在 POST 方法的顶部声明 ValidateAntiForgeryToken。
ValidateAntiForgeryToken
:此功能并不能阻止任何其他类型的数据伪造或篡改相关的攻击。要使用它,请使用 ValidateAntiForgeryToken
属性修饰操作方法或控制器,并在提交到该方法的表单中调用 @Html.AntiForgeryToken()
。
步骤 07:我们创建了一个名为“SignInRemember
”的方法来设置 Cookie 中的身份验证(记住我选项),以及一个名为“RedirectToLocal
”的方法来重定向到页面。
//GET: SignInAsync
private void SignInRemember(string userName, bool isPersistent = false)
{
// Clear any lingering authencation data
FormsAuthentication.SignOut();
// Write the authentication cookie
FormsAuthentication.SetAuthCookie(userName, isPersistent);
}
//GET: RedirectToLocal
private ActionResult RedirectToLocal(string returnURL = "")
{
try
{
// If the return url starts with a slash "/" we assume it belongs to our site
// so we will redirect to this "action"
if (!string.IsNullOrWhiteSpace(returnURL) && Url.IsLocalUrl(returnURL))
return Redirect(returnURL);
// If we cannot verify if the url is local to our host we redirect to a default location
return RedirectToAction("Index", "Dashboard");
}
catch
{
throw;
}
}
步骤 08:现在,所有必需的方法都已完成。最后,我们继续创建一个方法来验证用户名和密码。
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Login(LoginVM entity)
{
string OldHASHValue = string.Empty;
byte[] SALT = new byte[saltLengthLimit];
try
{
using (db = new DBEntities())
{
// Ensure we have a valid viewModel to work with
if (!ModelState.IsValid)
return View(entity);
//Retrive Stored HASH Value From Database According To Username (one unique field)
var userInfo = db.UserMasters.Where(s => s.Username == entity.Username.Trim()).FirstOrDefault();
//Assign HASH Value
if (userInfo != null)
{
OldHASHValue = userInfo.HASH;
SALT = userInfo.SALT;
}
bool isLogin = CompareHashValue(entity.Password, entity.Username, OldHASHValue, SALT);
if (isLogin)
{
//Login Success
//For Set Authentication in Cookie (Remeber ME Option)
SignInRemember(entity.Username, entity.isRemember);
//Set A Unique ID in session
Session["UserID"] = userInfo.UserID;
// If we got this far, something failed, redisplay form
// return RedirectToAction("Index", "Dashboard");
return RedirectToLocal(entity.ReturnURL);
}
else
{
//Login Fail
TempData["ErrorMSG"] = "Access Denied! Wrong Credential";
return View(entity);
}
}
}
catch
{
throw;
}
}
public static bool CompareHashValue(string password, string username, string OldHASHValue, byte[] SALT)
{
try
{
string expectedHashString = Get_HASH_SHA512(password, username, SALT);
return (OldHASHValue == expectedHashString);
}
catch
{
return false;
}
}
步骤 09:现在,最后阶段是创建一个自定义的 Authorize Attribute(授权属性)在 MVC 中。因为它会在任何页面初始化之前检查用户是否已登录。
在“Models 文件夹”中创建一个名为“CheckAuthorization.cs”的类。
public class CheckAuthorization : AuthorizeAttribute { public override void OnAuthorization(AuthorizationContext filterContext) { if (HttpContext.Current.Session["UserID"] == null || !HttpContext.Current.Request.IsAuthenticated) { if (filterContext.HttpContext.Request.IsAjaxRequest()) { filterContext.HttpContext.Response.StatusCode = 302; //Found Redirection to another page. Here- login page. Check Layout ajaxError() script. filterContext.HttpContext.Response.End(); } else { filterContext.Result = new RedirectResult(System.Web.Security.FormsAuthentication.LoginUrl + "?ReturnUrl=" + filterContext.HttpContext.Server.UrlEncode(filterContext.HttpContext.Request.RawUrl)); } } else { //Code HERE for page level authorization } } }
步骤 10:现在,我只需要将 [CheckAuthorization]
属性放在我的控制器顶部,即可访问我的 CheckAuthorization
函数,该函数是自定义的授权属性。
步骤 11:将身份验证模式添加到您的 Web.config。
//Add Below Code To Your Web.confing <system.web > <authentication mode = "Forms" > <forms loginUrl = "~/Home/Login" timeout = "2880" / > //Declare Your Return URL Here. Mean If Login Fail Then Page Redirect This URL </authentication> </system.web>