ASP.NET MVC 4 (十三) 基于表單的身份驗(yàn)證
在前面的章節(jié)中我們知道可以在MVC應(yīng)用程序中使用[Authorize]特性來限制用戶對(duì)某些網(wǎng)址(控制器/控制器方法)的訪問,但這都是在對(duì)用戶認(rèn)證之后,而用戶的認(rèn)證則依然是使用ASP.NET平臺(tái)的認(rèn)證機(jī)制。
ASP.NET提供Windows和Forms兩種身份驗(yàn)證,前者主要用于Intranet上域環(huán)境內(nèi),后者則更多的應(yīng)用于Internet,這里我們只討論后者。先從最簡單的例子開始,我們?cè)趙eb.config中配置Forms認(rèn)證方式:
... <authentication mode="Forms"> <forms loginUrl="~/Account/Login" timeout="2880"> <credentials passwordFormat="Clear"> <user name="admin" password="secret" /> </credentials> </forms> </authentication> ...
這里設(shè)置認(rèn)證方式為Forms,用戶登錄的地址為~/Account/Login,我們用最簡單的方式創(chuàng)建用戶信息,在credentials節(jié)中直接設(shè)置用戶名稱/密碼。在創(chuàng)建頁面之前我們先創(chuàng)建收集用戶名和密碼Model類:
using System.ComponentModel.DataAnnotations;
namespace SportsStore.WebUI.Models {
public class LoginViewModel {
[Required]
public string UserName { get; set; }
[Required]
[DataType(DataType.Password)]
public string Password { get; set; }
}
}
創(chuàng)建一個(gè)視圖來收集用戶名和信息:
@model SportsStore.WebUI.Models.LoginViewModel
@{
ViewBag.Title = "Admin: Log In";
Layout = "~/Views/Shared/_AdminLayout.cshtml";
}
<h1>Log In</h1>
<p>Please log in to access the administrative area:</p>
@using(Html.BeginForm()) {
@Html.ValidationSummary(true)
@Html.EditorForModel()
<p><input type="submit" value="Log in" /></p>
}
最后還需要在Account控制器的Login action中處理用戶提交的用戶名和密碼完成用戶認(rèn)證:
[HttpPost]
public ActionResult Login(LoginViewModel model)
{
if (ModelState.IsValid)
{
bool result = FormsAuthentication.Authenticate(model.UserName, model.Password);
if (result)
{
FormsAuthentication.SetAuthCookie(model.UserName, false);
return Redirect(Url.Action("Index", "Admin"));
}
else
{
ModelState.AddModelError("", "Incorrect username or password");
return View();
}
}
else
{
return View();
}
}
調(diào)用FormsAuthentication.Authenticate()對(duì)用戶名和密碼驗(yàn)證,如何驗(yàn)證成功,調(diào)用FormsAuthentication.SetAuthCookie()設(shè)置用戶驗(yàn)證的cookie并在響應(yīng)中返回,在cookie過期之前用戶再次訪問時(shí)不再要求登錄。
以上就是最簡單的Forms身份驗(yàn)證過程,但實(shí)際的Internet應(yīng)用用戶的信息一般存儲(chǔ)在數(shù)據(jù)庫中,通過Membership provider利用數(shù)據(jù)庫中的信息對(duì)用戶驗(yàn)證,MVC4中微軟為我們提供SQL membership provider、Universal membership provider和Simple membership provider,下面來看看如何具體如何使用它們。
SQL membership provider
在.NET 2.0中SQL membership provider就已經(jīng)存在了,在visual studio 2012中使用empty模板創(chuàng)建一個(gè)MVC4的工程,web.config你不會(huì)看到任何membership provider相關(guān)的信息,默認(rèn)使用的是Windows認(rèn)證。在VS的Project菜單下打開Asp.net configurtion工具(在打開配置工具前記得編譯下工程,否則會(huì)提示“選定的數(shù)據(jù)存儲(chǔ)區(qū)出現(xiàn)問題”),在“安全”標(biāo)簽頁面點(diǎn)擊“選擇身份驗(yàn)證類型”,配置工具詢問“用戶如何訪問您的站點(diǎn)?”,選擇“通過Internet”,點(diǎn)擊“完成”后配置工具將自動(dòng)在web.config中添加“<authentication mode="Forms" />”。配置工具仍然沒有在web.config添加任何membership provider的信息,但是我們轉(zhuǎn)到配置工具的“提供程序頁面”,可以看到看到默認(rèn)選中的是AspNetSqlMembershipProvider,同時(shí)配置工具會(huì)在工程的app_data目錄下創(chuàng)建一個(gè)名為ASPNETDB.MDF的數(shù)據(jù)庫,這是一個(gè)sql express的數(shù)據(jù)庫,visual studio 2012中不能直接打開(VS用的是localdb),可以在SQL管理工具中附加到SQL EXPRESS的服務(wù)實(shí)例來查看。打開數(shù)據(jù)庫可以看到數(shù)據(jù)庫中添加了很多“aspnet_”為前綴的表和存儲(chǔ)過程,這些都是SqlMembershipProvider需要的。
如果我們要使用自建的數(shù)據(jù)庫來保存用戶信息改如何操作呢?我們?cè)赟olution exploer中點(diǎn)擊App_Start目錄,右鍵菜單中選擇添加->添加項(xiàng)目->SQL數(shù)據(jù)庫創(chuàng)建一個(gè)localdb的數(shù)據(jù)庫,添加相應(yīng)的Connection字符串到web.config:
<connectionStrings>
<add name="DefaultConnection" connectionString="Data Source=(localdb)\v11.0;AttachDbFileName=|DataDirectory|\mvc4empty.mdf;Initial Catalog=mvc4empty;Integrated Security=True"
providerName="System.Data.SqlClient"/>
</connectionStrings>
我們還需要在web.config手工添加SqlMembershipProvider,讓它使用上面的數(shù)據(jù)庫連接:
<membership defaultProvider="mySqlMembershipProvider">
<providers>
<clear />
<add connectionStringName="DefaultConnection" enablePasswordRetrieval="false"
enablePasswordReset="true" requiresQuestionAndAnswer="false"
applicationName="/" requiresUniqueEmail="false" passwordFormat="Hashed"
passwordStrengthRegularExpression="" name="mySqlMembershipProvider"
type="System.Web.Security.SqlMembershipProvider" />
</providers>
</membership>
再次打開asp.net配置工具轉(zhuǎn)到安全界面會(huì)提示錯(cuò)誤“Could not find stored procedure 'dbo.aspnet_CheckSchemaVersion'”,配置工具試圖調(diào)用相關(guān)的存儲(chǔ)過程,但是數(shù)據(jù)庫是我們手工創(chuàng)建的,不包含這些過程和數(shù)據(jù)表。我們可以使用aspnet_regsql.exe工具在我們的數(shù)據(jù)庫中創(chuàng)建相關(guān)表和數(shù)據(jù),C:\Windows\Microsoft.NET\Framework64\v4.0.30319和C:\Windows\Microsoft.NET\Framework64\v2.0.50727都有這個(gè)工具,沒有細(xì)究兩個(gè)版本的不同,這里使用.NET 4.0的版本。在aspnet_regsql工具選擇服務(wù)器為“(localdb)\v11.0”,數(shù)據(jù)庫列表中如果找不到新建的數(shù)據(jù)庫,可以事先在sql manage studio中連接到服務(wù)引擎“(localdb)\v11.0”后附加該數(shù)據(jù)庫(aspnet_reqsql也支持使用連接字符串作為參數(shù),參見http://msdn.microsoft.com/en-us/library/ms229862(v=vs.100).aspx)。完成上述操作后,asp.net配置工具就可以在我們的數(shù)據(jù)庫中創(chuàng)建管理用戶了。
準(zhǔn)備好Forms認(rèn)證的配置,我們繼續(xù)完善上面的例子,從控制器開始:
using System;
using System.Web.Mvc;
using System.Web.Security;
using SportsStore.WebUI.Models;
namespace SportsStore.WebUI.Controllers
{
public class AccountController : Controller
{
public ViewResult Login(string returnUrl = null)
{
ViewBag.ReturnUrl = returnUrl;
return View();
}
[HttpPost]
public ActionResult Login(LoginViewModel model, string returnUrl)
{
if (!ModelState.IsValid) return View();
var result = Membership.ValidateUser(model.UserName, model.Password);
if (result)
{
FormsAuthentication.SetAuthCookie(model.UserName, false);
return Redirect(returnUrl ?? Url.Action("Index", "Home"));
}
ModelState.AddModelError("", "Incorrect username or password");
return View();
}
public ActionResult Logout(string returnUrl)
{
FormsAuthentication.SignOut();
return Redirect(returnUrl ?? Url.Action("Index", "Home"));
}
public ViewResult Register()
{
return View();
}
[HttpPost]
public ViewResult Register(LoginViewModel model)
{
if (!ModelState.IsValid) return View(model);
try
{
Membership.CreateUser(model.UserName, model.Password);
ViewBag.Registered = true;
}
catch (Exception exception)
{
ModelState.AddModelError("",exception.Message);
}
return View(model);
}
}
}
在用戶登錄時(shí)不再使用FormsAuthentication.Authenticate()認(rèn)證用戶,它僅讀取web.config中credentials節(jié)的內(nèi)容,我們需要改用Membership.ValidateUser()對(duì)用戶密碼校驗(yàn)。調(diào)用FormsAuthentication.SignOut()登出用戶,它清除認(rèn)證相關(guān)的cookie。Register() action用于創(chuàng)建用戶,它調(diào)用Membership.CreateUser()創(chuàng)建一個(gè)用戶保存到數(shù)據(jù)庫中,對(duì)應(yīng)的Register視圖:
@model SportsStore.WebUI.Models.LoginViewModel
@{
ViewBag.Title = "User: Register";
Layout = "~/Views/Shared/_AdminLayout.cshtml";
}
<h1>User register</h1>
@if (ViewBag.Registered != null && ViewBag.Registered)
{
<p>User "@Model.UserName" has been created sucessfully!</p>
}
else
{
<p>Please input user name and password to register:</p>
using (Html.BeginForm())
{
@Html.ValidationSummary(true)
@Html.EditorForModel()
<p>
<input type="submit" value="Register" /></p>
}
}
作為示例這里簡單的收集用戶名和密碼,成功注冊(cè)后給出提示,Html.ValidationSummary()顯示發(fā)生的錯(cuò)誤發(fā)生,比如用戶名已經(jīng)存在。我們可以在布局文件中創(chuàng)建一些鏈接關(guān)聯(lián)到用戶注冊(cè)、登出:
...
<div>
@if (User.Identity.IsAuthenticated)
{
<p>Current user:@User.Identity.Name</p>
@Html.RouteLink("Logout",new {Controller="Account",Action="Logout",returnUrl=Request.Url.PathAndQuery})
}
else
{
<span>@Html.RouteLink("Login",new {Controller="Account",Action="Login",returnUrl=Request.Url.PathAndQuery})</span>
<span>@Html.ActionLink("Register","Register","Account")</span>
}
</div>
<div>
@if (User.IsInRole("Admin"))
{
@Html.ActionLink("Administrate", "Index", "Admin")
}
</div>
...
Universal provider
SQL membership provider要求使用完整安裝的SQL server,使用到很多表和存儲(chǔ)過程,對(duì)SQL server azure、SQL server compact都不支持,于是Universal provider出現(xiàn)了,最早于 2011年發(fā)布。我們可以在VS2012中使用Basic模板創(chuàng)建MVC4工程,工程被配置為默認(rèn)使用Universal provider。我們也可以在nuget包管理器搜索“universal”,找到“Microsoft ASP.NET universal provider”安裝,安裝工具修改web.config配置DefaultMembershipProvider作為默認(rèn)的provider;配置EntityFramework,universal provider使用EntityFramework完成數(shù)據(jù)庫的讀寫;創(chuàng)建一個(gè)SQL express的數(shù)據(jù)庫和連接字符串供universal provider使用。下面是web.config的部分內(nèi)容:
...
<configSections>
<!-- For more information on Entity Framework configuration, visit http://go.microsoft.com/fwlink/?LinkID=237468 -->
<section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
</configSections>
...
<profile defaultProvider="DefaultProfileProvider">
<providers>
<add name="DefaultProfileProvider" type="System.Web.Providers.DefaultProfileProvider, System.Web.Providers, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" connectionStringName="DefaultConnection" applicationName="/" />
</providers>
</profile>
<membership defaultProvider="DefaultMembershipProvider">
<providers>
<add name="DefaultMembershipProvider" type="System.Web.Providers.DefaultMembershipProvider, System.Web.Providers, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" connectionStringName="DefaultConnection" enablePasswordRetrieval="false" enablePasswordReset="true" requiresQuestionAndAnswer="false" requiresUniqueEmail="false" maxInvalidPasswordAttempts="5" minRequiredPasswordLength="6" minRequiredNonalphanumericCharacters="0" passwordAttemptWindow="10" applicationName="/" passwordFormat="Hashed" passwordStrengthRegularExpression="" />
</providers>
</membership>
<roleManager defaultProvider="DefaultRoleProvider">
<providers>
<add name="DefaultRoleProvider" type="System.Web.Providers.DefaultRoleProvider, System.Web.Providers, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" connectionStringName="DefaultConnection" applicationName="/" />
</providers>
</roleManager>
<!--
If you are deploying to a cloud environment that has multiple web server instances,
you should change session state mode from "InProc" to "Custom". In addition,
change the connection string named "DefaultConnection" to connect to an instance
of SQL Server (including SQL Azure and SQL Compact) instead of to SQL Server Express.
-->
<sessionState mode="InProc" customProvider="DefaultSessionProvider">
<providers>
<add name="DefaultSessionProvider" type="System.Web.Providers.DefaultSessionStateProvider, System.Web.Providers, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" connectionStringName="DefaultConnection" />
</providers>
</sessionState>
....
<entityFramework>
<defaultConnectionFactory type="System.Data.Entity.Infrastructure.SqlConnectionFactory, EntityFramework" />
<providers>
<provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" />
</providers>
</entityFramework>
...
打開asp.net配置工具,可以看到成員資格提供程序有AspNetSqlMembershipProvider和DefaultMembershipProvider供選擇,前者就是sql membership provider,我們我們這時(shí)選擇它,配置工具會(huì)把membership改為:
<membership>
defaultProvider特性被刪除,不帶任何的特性,這需要特別注意。
查看Universal provider生成的數(shù)據(jù)庫,它只包含Users、Roles、Profiles、Memberships、UsersInRoles、Applications幾個(gè)表,而且沒有任何的存儲(chǔ)過程,確實(shí)很多程度上簡化了數(shù)據(jù)庫模型,不再使用存儲(chǔ)過程操作數(shù)據(jù),因此支持的SQL服務(wù)類型也更多。nuget包安裝工具為我們自動(dòng)創(chuàng)建了一個(gè)數(shù)據(jù)庫,如果我們要使用原有的數(shù)據(jù)庫該怎么辦呢?我們只需要改動(dòng)相應(yīng)的連接字符串,編譯后啟動(dòng)asp.net配置工具,它會(huì)在我們?cè)械臄?shù)據(jù)庫中創(chuàng)建上面的幾個(gè)表。
SQL membership provider一節(jié)示例的的控制器/視圖我們不需要任何改動(dòng)都可以在切換成universal provider后正常運(yùn)行,對(duì)Membership方法的調(diào)用在MVC內(nèi)部轉(zhuǎn)由System.Web.Providers.DefaultProfileProvider,對(duì)我們寫程序講沒有任何不同。這樣講似乎universal provider沒有帶來太多的好處,實(shí)際上隨著數(shù)據(jù)庫結(jié)構(gòu)的簡化,對(duì)我們擴(kuò)展profile等有很大的便利,這里就不再深入討論。
Simple provider
simple provider在VS 2010 SP1中隨Webmatrix發(fā)布,和universal provider一樣使用entrity framework操作用戶信息數(shù)據(jù)庫,但是數(shù)據(jù)庫的結(jié)構(gòu)更為簡單也可以更為靈活的配置。在VS2012中我們使用Internet模板創(chuàng)建MVC4的工程,工程被配置為使用simple provider。web.config中只有<authentication mode="Forms">,不再包含membership provider的信息,membership的處理直接在控制器中使用WebMatrix.WebData.WebSecurity處理。Internet模板創(chuàng)建了具備完整用戶功能的代碼,這里不一一列出。
Internet模板創(chuàng)建一個(gè)名為InitializeSimpleMembershipAttribute的過濾器,它在每次應(yīng)用程序啟動(dòng)時(shí)調(diào)用一次:
WebSecurity.InitializeDatabaseConnection("DefaultConnection", "UserProfile", "UserId", "UserName", autoCreateTables: true);
這個(gè)方法使初始化用戶信息的數(shù)據(jù)庫連接,DefaultConnection為數(shù)據(jù)庫的連接字符串,Userpofile為表名稱,UserId和UserName分別為用戶ID和用戶名稱在表中的字段名稱,也就是說我們只需要一個(gè)最簡單的有用戶ID和名稱兩個(gè)字段的表就可以了,這個(gè)表可以在任何數(shù)據(jù)庫中,這是可以動(dòng)態(tài)設(shè)置的,所以asp.net的配置工具不能用于配置simple provider。
Internet模板創(chuàng)建Account控制器,包含眾多action方法用于提供用戶注冊(cè)、登錄、登出、修改密碼,基本上都是調(diào)用WebSecurity的相關(guān)方法來實(shí)現(xiàn)的,比如登錄調(diào)用的是WebSecurity.Login()。在Internet模板的基礎(chǔ)上,我們可以很方便的自定義profile、roles等,這里也不再深入,已經(jīng)有一篇很好的文章講解simple provider如何工作,可以參見http://weblogs.asp.net/jgalloway/archive/2012/08/29/simplemembership-membership-providers-universal-providers-and-the-new-asp-net-4-5-web-forms-and-asp-net-mvc-4-templates.aspx。
MVC5已隨VS2013在2013十月發(fā)布,相對(duì)于MVC4有了很多的變化,包括這里所講的安全認(rèn)證。就以本文結(jié)束MVC4,開始MVC5之旅。

浙公網(wǎng)安備 33010602011771號(hào)