在前面的章节中我们知道可以在MVC应用程序中使用[Authorize]特性来限制用户对某些网址(控制器/控制器方法)的访问,但这都是在对用户认证之后,而用户的认证则依然是使用ASP.NET平台的认证机制。

ASP.NET提供Windows和Forms两种身份验证,前者主要用于Intranet上域环境内,后者则更多的应用于Internet,这里我们只讨论后者。先从最简单的例子开始,我们在web.config中配置Forms认证方式:

...
<authentication mode="Forms">
  <forms loginUrl="~/Account/Login" timeout="2880">
    <credentials passwordFormat="Clear">
      <user name="admin" password="secret" />
    </credentials>
  </forms>
</authentication>
...

这里设置认证方式为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";
} <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中处理用户提交的用户名和密码完成用户认证:

[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:

<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,让它使用上面的数据库连接:

<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配置工具转到安全界面会提示错误“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";
} <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>
}
}

作为示例这里简单的收集用户名和密码,成功注册后给出提示,Html.ValidationSummary()显示发生的错误发生,比如用户名已经存在。我们可以在布局文件中创建一些链接关联到用户注册、登出:

...
<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,使用到很多表和存储过程,对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的部分内容:

...
<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,我们我们这时选择它,配置工具会把membership改为:

<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如何工作,可以参见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十月发布,相对于MVC4有了很多的变化,包括这里所讲的安全认证。就以本文结束MVC4,开始MVC5之旅。

ASP.NET MVC 4 (十三) 基于表单的身份验证的更多相关文章

  1. 【FBA】SharePoint 2013自定义Providers在基于表单的身份验证(Forms-Based-Authentication)中的应用

    //http://www.cnblogs.com/OceanEyes/p/custom-provider-in-sharepoint-2013-fba-authentication.html 由于项目 ...

  2. [FBA]SharePoint 2013自定义Providers在基于表单的身份验证(Forms-Based-Authentication)中的应用

    //http://tech.ddvip.com/2014-05/1401197453210723.html 由于项目的需要,登录SharePoint Application的用户将从一个统一平台中获取 ...

  3. SharePoint 2013自定义Providers在基于表单的身份验证(Forms-Based-Authentication)中的应用

    由于项目的需要,登录SharePoint Application的用户将从一个统一平台中获取,而不是从Domain中获取,所以需要对SharePoint Application的身份验证(Claims ...

  4. asp.net 如何配置authentication,完成基于表单的身份验证

    步骤一: 在根目录下的web.config中加入: <system.web> <authentication mode="Forms">           ...

  5. 基于表单的身份验证(FBA)

    https://technet.microsoft.com/zh-cn/library/ee806890(office.15).aspx http://www.tuicool.com/articles ...

  6. JavaWeb应用中的身份验证(声明式)——基于表单的身份认证

    容器管理安全最普遍的类型建立在基于表单的身份验证方式上. 通过这样的方式,server自己主动将尚未验证的用户重定向到一个HTML表单.检查他们的username和password,决定他们属于哪个角 ...

  7. SharePoint 2013 配置基于表单的身份认证

    前 言 这里简单介绍一下为SharePoint 2013 配置基于表单的身份认证,简单的说,就是用Net提供的工具创建数据库,然后配置SharePoint 管理中心.STS服务.Web应用程序的三处w ...

  8. 在Tomcat中采用基于表单的安全验证

    .概述   (1)基于表单的验证 基于From的安全认证可以通过TomcatServer对Form表单中所提供的数据进行验证,基于表单的验证使系统开发者可以自定义用户的登陆页面和报错页面.这种验证方法 ...

  9. 如何在.Net Core MVC中为动态表单开启客户端验证

    非Core中的请参照: MVC的验证 jquery.validate.unobtrusive mvc验证jquery.unobtrusive-ajax 参照向动态表单增加验证 页面引入相关JS: &l ...

随机推荐

  1. CentOS 7卸载Docker

    1.先查询所有安装的包 yum list installed | grep docker*或者rpm -qa docker* 2.删除查询出来的包 # 一般情况会有一个 yum remove -y d ...

  2. 【转】Intellij IDEA调试功能

    http://www.cnblogs.com/winner-0715/p/5422952.html 先编译好要调试的程序.1.设置断点

  3. conda虚拟环境

    https://blog.csdn.net/lyy14011305/article/details/59500819 1.首先在所在系统中安装Anaconda.可以打开命令行输入conda -V检验是 ...

  4. 使用Jenkins搭建持续集成(CI)环境

    转自:http://www.cnitblog.com/luckydmz/archive/2012/01/03/77007.html 首先从官网http://jenkins-ci.org/下载 Java ...

  5. SqlServer 对分组的内容进行拼接-group_concat

    SqlServer  对分组的内容进行拼接: 方案1:xml 子集,性能较差 方案2:借助 sqlCLR 接入.实现group_concat.性能完美,但是 阿里云的不支持!!!! CREATE TA ...

  6. Mongodb 笔记 - 性能及Java代码

    性能 以下数据都是在千兆网络下测试的结果 写入 数据量的增大会导致内存占满, 因为mongodb会将数据尽可能地载入内存, 索引占用的空间也很可观非安全模式下, 速度取决于内存是否占满能差一个数量级, ...

  7. [Aaronyang] 写给自己的WPF4.5 笔记17[Page实现页面导航]

    1. 第一个Page页使用 新建PageDemo解决方案,默认wpf应用程序 右键项目新建页,然后指定App.xaml的默认启动窗口,为Page1.xaml,F5运行项目 文章内容已经迁移http:/ ...

  8. Nginx 访问认证

    简介 在实际工作中,企业中有些网站,要求使用账号和密码才能访问,如网站后台.phpMyAdmin .Wiki 平台 等 模块ngx_http_auth_basic_module 允许使用"H ...

  9. Eclipse插件的安装(手动安装),以安装SVN插件和中文语言包为例

    Eclipse 插件的手动配置 今天自己亲自手动安装了Eclipse插件,参考了网络上的一些文章,总结一下安装的方法.下面通过两个例子来分享一下自己的收获. 例1:SVN插件安装 1.在Eclipse ...

  10. Atitit  验证 数字验证 非空验证的最佳算法  h5

    Atitit  验证 数字验证 非空验证的最佳算法  h5 <td><select class="searchBox-select"   style=" ...