【IT168技术】这两天在给一个客户讲解MVC 3的架构和在项目中的应用,有提到这样一个问题:MVC3 默认实现的Forms Authentication机制,可以结合SQL Server做成员管理,而且可以设置在一定时间内如果用户连续输入密码错误达到一定次数的话,就自动将用户锁定。
但是,默认情况下,却没有提供一定的机制,给用户提示,例如你已经输入几次失败了,还最多可以输入几次等等。这在有的时候给用户造成了一些不便。
那么,是否有办法解决这个问题呢?
首先,我们来看一下默认的一些设置和代码
MemberShip的配置
<providers>
<clear/>
<add name="AspNetSqlMembershipProvider" type="System.Web.Security.SqlMembershipProvider" connectionStringName="ApplicationServices"
enablePasswordRetrieval="false" enablePasswordReset="true" requiresQuestionAndAnswer="false" requiresUniqueEmail="false"
maxInvalidPasswordAttempts="5" minRequiredPasswordLength="6" minRequiredNonalphanumericCharacters="0" passwordAttemptWindow="10"
applicationName="/" />
</providers>
</membership>
上面的配置是默认的,意思是在10分钟内,如果连续输入密码5次错误的话,就锁住用户
AccountController中的代码
public ActionResult LogOn(LogOnModel model, string returnUrl)
{
if(ModelState.IsValid)
{
if(Membership.ValidateUser(model.UserName, model.Password))
{
FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe);
if(Url.IsLocalUrl(returnUrl) && returnUrl.Length > 1 && returnUrl.StartsWith("/")
&& !returnUrl.StartsWith("//") && !returnUrl.StartsWith("/\\"))
{
return Redirect(returnUrl);
}
else
{
return RedirectToAction("Index", "Home");
}
}
else
{
ModelState.AddModelError("", "The user name or password provided is incorrect.");
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
这里的代码很简单,只要不通过验证的话,就显示一个错误消息:The user name or password provided is incorrect.
但其实这个消息对用户来说并不是很直观,他不会知道到底是哪里出了错:是用户名不对呢?还是密码不对?甚至说,密码还可以最多输入几次?
所以,默认情况下,如果登录不成功,就会看到如下的界面

如果连续输入错误超过5次,就会导致用户再也无法登录,而他没有得到任何提示。我们通过数据库可以看到这个用户已经被锁定了。

而且在这个表中确实有一个字段是记录了连续输入密码错误的次数

那么,接下来看看是否有什么办法解决?我想到的解决方案如下
添加一个自定义的MembershipProvider
既然默认的Membership Provider提供不了这个功能,那么我们可以尝试自己做一个,想办法去读取这张表应该就能实现
using System.Data.SqlClient;
using System.Web.Security;
namespace MvcApplicationSample.Extensions
{
/// <summary>
/// 这是一个自定义的MembershipProvider,添加了一个功能,可以获取用户连续输入密码失败的次数
/// 作者:陈希章
/// 反馈:ares@xizhang.com
/// </summary>
public class MySQLMemberShipProvider:SqlMembershipProvider
{
private string connectionString = string.Empty;
public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config)
{
base.Initialize(name, config);
//这里获取到默认使用的数据库连接字符串
connectionString = ConfigurationManager.ConnectionStrings["ApplicationServices"].ConnectionString;
}
public int GetFailedPasswordAttemptCount(string userName)
{
using(var conn = new SqlConnection(connectionString))
{
using(var cmd = conn.CreateCommand())
{
cmd.CommandText = "SELECT aspnet_Membership.FailedPasswordAttemptCount FROM aspnet_Membership INNER JOIN aspnet_Users ON aspnet_Membership.UserId = aspnet_Users.UserId INNER JOIN aspnet_Applications ON aspnet_Membership.ApplicationId =aspnet_Applications.ApplicationId AND aspnet_Users.ApplicationId = aspnet_Applications.ApplicationId WHERE (aspnet_Users.UserName = @userName) AND (aspnet_Applications.ApplicationName =@applicationName)";
cmd.Parameters.AddWithValue("@userName", userName);
cmd.Parameters.AddWithValue("@applicationName", this.ApplicationName);
conn.Open();
var result = cmd.ExecuteScalar();
conn.Close();
if(result != null)
{
return (int)result;
}
}
}
//如果用户不存在,则返回-1
return -1;
}
}
}
修改web.config使用这个新的membership Provider
修改部分为下面粗体的部分
<providers>
<clear/>
<add name="AspNetSqlMembershipProvider" type="MvcApplicationSample.Extensions.MySQLMemberShipProvider,MvcApplicationSample" connectionStringName="ApplicationServices"
enablePasswordRetrieval="false" enablePasswordReset="true" requiresQuestionAndAnswer="false" requiresUniqueEmail="false"
maxInvalidPasswordAttempts="5" minRequiredPasswordLength="6" minRequiredNonalphanumericCharacters="0" passwordAttemptWindow="10"
applicationName="/" />
</providers>
</membership>
修改AccountController中的代码(请注意粗体部分)
public ActionResult LogOn(LogOnModel model, string returnUrl)
{
if(ModelState.IsValid)
{
if(Membership.ValidateUser(model.UserName, model.Password))
{
FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe);
if(Url.IsLocalUrl(returnUrl) && returnUrl.Length > 1 && returnUrl.StartsWith("/")
&& !returnUrl.StartsWith("//") && !returnUrl.StartsWith("/\\"))
{
return Redirect(returnUrl);
}
else
{
return RedirectToAction("Index", "Home");
}
}
else
{
var provider = (Extensions.MySQLMemberShipProvider)Membership.Provider;
var count = provider.GetFailedPasswordAttemptCount(model.UserName);
var max = provider.MaxInvalidPasswordAttempts;
if(count != -1)
{
ModelState.AddModelError("", count==max?"Your account is locked.":string.Format("You have been continually input the wrong password {0} times, if then enter {1} mistakes, your account will be locked", count, max - count));
}
else
ModelState.AddModelError("", "The user name or password provided is incorrect.");
}
}
// If we got this far, something failed, redisplay form
return View(model);
}
这样的话,我们来测试一下效果看看

如果连续输错5次,则提示已经被锁定了