在前面的章节中我们知道可以在MVC应用程序中使用[Authorize]特性来限制用户对某些网址(控制器/控制器方法)的访问,但这都是在对用户认证之后,而用户的认证则依然是使用ASP.NET平台的认证机制。
ASP.NET提供Windows和Forms两种身份验证,前者主要用于Intranet上域环境内,后者则更多的应用于Internet,这里我们只讨论后者。先从最简单的例子开始,我们在web.config中配置Forms认证方式:
......
这里设置认证方式为Forms,用户登录的地址为~/Account/Login,我们用最简单的方式创建用户信息,在credentials节中直接设置用户名称/密码。在创建页面之前我们先创建收集用户名和密码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; } }}
创建一个视图来收集用户名和信息:
@model SportsStore.WebUI.Models.LoginViewModel@{ ViewBag.Title = "Admin: Log In"; Layout = "~/Views/Shared/_AdminLayout.cshtml";}Log In
Please log in to access the administrative area:
@using(Html.BeginForm()) { @Html.ValidationSummary(true) @Html.EditorForModel() }
最后还需要在Account控制器的Login action中处理用户提交的用户名和密码完成用户认证:
[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(); } }
调用FormsAuthentication.Authenticate()对用户名和密码验证,如何验证成功,调用FormsAuthentication.SetAuthCookie()设置用户验证的cookie并在响应中返回,在cookie过期之前用户再次访问时不再要求登录。
以上就是最简单的Forms身份验证过程,但实际的Internet应用用户的信息一般存储在数据库中,通过Membership provider利用数据库中的信息对用户验证,MVC4中微软为我们提供SQL membership provider、Universal membership provider和Simple membership provider,下面来看看如何具体如何使用它们。
SQL membership provider
在.NET 2.0中SQL membership provider就已经存在了,在visual studio 2012中使用empty模板创建一个MVC4的工程,web.config你不会看到任何membership provider相关的信息,默认使用的是Windows认证。在VS的Project菜单下打开Asp.net configurtion工具(在打开配置工具前记得编译下工程,否则会提示“选定的数据存储区出现问题”),在“安全”标签页面点击“选择身份验证类型”,配置工具询问“用户如何访问您的站点?”,选择“通过Internet”,点击“完成”后配置工具将自动在web.config中添加“<authentication mode="Forms" />”。配置工具仍然没有在web.config添加任何membership provider的信息,但是我们转到配置工具的“提供程序页面”,可以看到看到默认选中的是AspNetSqlMembershipProvider,同时配置工具会在工程的app_data目录下创建一个名为ASPNETDB.MDF的数据库,这是一个sql express的数据库,visual studio 2012中不能直接打开(VS用的是localdb),可以在SQL管理工具中附加到SQL EXPRESS的服务实例来查看。打开数据库可以看到数据库中添加了很多“aspnet_”为前缀的表和存储过程,这些都是SqlMembershipProvider需要的。
如果我们要使用自建的数据库来保存用户信息改如何操作呢?我们在Solution exploer中点击App_Start目录,右键菜单中选择添加->添加项目->SQL数据库创建一个localdb的数据库,添加相应的Connection字符串到web.config:
我们还需要在web.config手工添加SqlMembershipProvider,让它使用上面的数据库连接:
再次打开asp.net配置工具转到安全界面会提示错误“Could not find stored procedure 'dbo.aspnet_CheckSchemaVersion'”,配置工具试图调用相关的存储过程,但是数据库是我们手工创建的,不包含这些过程和数据表。我们可以使用aspnet_regsql.exe工具在我们的数据库中创建相关表和数据,C:\Windows\Microsoft.NET\Framework64\v4.0.30319和C:\Windows\Microsoft.NET\Framework64\v2.0.50727都有这个工具,没有细究两个版本的不同,这里使用.NET 4.0的版本。在aspnet_regsql工具选择服务器为“(localdb)\v11.0”,数据库列表中如果找不到新建的数据库,可以事先在sql manage studio中连接到服务引擎“(localdb)\v11.0”后附加该数据库(aspnet_reqsql也支持使用连接字符串作为参数,参见http://msdn.microsoft.com/en-us/library/ms229862(v=vs.100).aspx)。完成上述操作后,asp.net配置工具就可以在我们的数据库中创建管理用户了。
准备好Forms认证的配置,我们继续完善上面的例子,从控制器开始:
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); } }}
在用户登录时不再使用FormsAuthentication.Authenticate()认证用户,它仅读取web.config中credentials节的内容,我们需要改用Membership.ValidateUser()对用户密码校验。调用FormsAuthentication.SignOut()登出用户,它清除认证相关的cookie。Register() action用于创建用户,它调用Membership.CreateUser()创建一个用户保存到数据库中,对应的Register视图:
@model SportsStore.WebUI.Models.LoginViewModel@{ ViewBag.Title = "User: Register"; Layout = "~/Views/Shared/_AdminLayout.cshtml";}User register
@if (ViewBag.Registered != null && ViewBag.Registered){User "@Model.UserName" has been created sucessfully!
}else{Please input user name and password to register:
using (Html.BeginForm()) { @Html.ValidationSummary(true) @Html.EditorForModel()}}
作为示例这里简单的收集用户名和密码,成功注册后给出提示,Html.ValidationSummary()显示发生的错误发生,比如用户名已经存在。我们可以在布局文件中创建一些链接关联到用户注册、登出:
...@if (User.Identity.IsAuthenticated) {Current user:@User.Identity.Name
@Html.RouteLink("Logout",new {Controller="Account",Action="Logout",returnUrl=Request.Url.PathAndQuery}) } else { @Html.RouteLink("Login",new {Controller="Account",Action="Login",returnUrl=Request.Url.PathAndQuery}) @Html.ActionLink("Register","Register","Account") }@if (User.IsInRole("Admin")) { @Html.ActionLink("Administrate", "Index", "Admin") }...
Universal provider
SQL membership provider要求使用完整安装的SQL server,使用到很多表和存储过程,对SQL server azure、SQL server compact都不支持,于是Universal provider出现了,最早于 2011年发布。我们可以在VS2012中使用Basic模板创建MVC4工程,工程被配置为默认使用Universal provider。我们也可以在nuget包管理器搜索“universal”,找到“Microsoft ASP.NET universal provider”安装,安装工具修改web.config配置DefaultMembershipProvider作为默认的provider;配置EntityFramework,universal provider使用EntityFramework完成数据库的读写;创建一个SQL express的数据库和连接字符串供universal provider使用。下面是web.config的部分内容:
...... .... ...
打开asp.net配置工具,可以看到成员资格提供程序有AspNetSqlMembershipProvider和DefaultMembershipProvider供选择,前者就是sql membership provider,我们我们这时选择它,配置工具会把membership改为:
defaultProvider特性被删除,不带任何的特性,这需要特别注意。
查看Universal provider生成的数据库,它只包含Users、Roles、Profiles、Memberships、UsersInRoles、Applications几个表,而且没有任何的存储过程,确实很多程度上简化了数据库模型,不再使用存储过程操作数据,因此支持的SQL服务类型也更多。nuget包安装工具为我们自动创建了一个数据库,如果我们要使用原有的数据库该怎么办呢?我们只需要改动相应的连接字符串,编译后启动asp.net配置工具,它会在我们原有的数据库中创建上面的几个表。
SQL membership provider一节示例的的控制器/视图我们不需要任何改动都可以在切换成universal provider后正常运行,对Membership方法的调用在MVC内部转由System.Web.Providers.DefaultProfileProvider,对我们写程序讲没有任何不同。这样讲似乎universal provider没有带来太多的好处,实际上随着数据库结构的简化,对我们扩展profile等有很大的便利,这里就不再深入讨论。
Simple provider
simple provider在VS 2010 SP1中随Webmatrix发布,和universal provider一样使用entrity framework操作用户信息数据库,但是数据库的结构更为简单也可以更为灵活的配置。在VS2012中我们使用Internet模板创建MVC4的工程,工程被配置为使用simple provider。web.config中只有<authentication mode="Forms">,不再包含membership provider的信息,membership的处理直接在控制器中使用WebMatrix.WebData.WebSecurity处理。Internet模板创建了具备完整用户功能的代码,这里不一一列出。
Internet模板创建一个名为InitializeSimpleMembershipAttribute的过滤器,它在每次应用程序启动时调用一次:
WebSecurity.InitializeDatabaseConnection("DefaultConnection", "UserProfile", "UserId", "UserName", autoCreateTables: true);
这个方法使初始化用户信息的数据库连接,DefaultConnection为数据库的连接字符串,Userpofile为表名称,UserId和UserName分别为用户ID和用户名称在表中的字段名称,也就是说我们只需要一个最简单的有用户ID和名称两个字段的表就可以了,这个表可以在任何数据库中,这是可以动态设置的,所以asp.net的配置工具不能用于配置simple provider。
Internet模板创建Account控制器,包含众多action方法用于提供用户注册、登录、登出、修改密码,基本上都是调用WebSecurity的相关方法来实现的,比如登录调用的是WebSecurity.Login()。在Internet模板的基础上,我们可以很方便的自定义profile、roles等,这里也不再深入,已经有一篇很好的文章讲解simple provider如何工作,可以参见。
MVC5已随VS2013在2013十月发布,相对于MVC4有了很多的变化,包括这里所讲的安全认证。就以本文结束MVC4,开始MVC5之旅。