c# - 如果 5 次尝试失败,则锁定用户

标签 c# sql asp.net-mvc

我已经可以减少 AttemptsLeft在数据库中设置为 5AttemptsLeft如果我使用正确的用户名输入错误的密码,则会减少 1。如果用户的状态是Suspended,我也已经可以让用户不登录系统.

问题:

我设置的时候AttemptsLeft减少到0,用户的状态将变为Suspended 。然而,AttemptsLeft数据库中的信息总是会减少,用户的状态不会变成Suspended ,但它会得到 Suspended如果我正确输入用户名和密码,无论AttemptsLeft有多少个左。

可能出了什么问题?

我认为user.Attempts从数据库检索不起作用,这是因为 user.Attempts永远是0 ,只有AttemptsLeft数据库中的内容将会减少,因为我将其减少 query .

这是我正在使用的代码:

UserManager 类:

public void GetAttempts(string Username, string Password)
        {
            LoginContext context = new LoginContext();

            using (SqlConnection conn = new SqlConnection(connectionString))
            {
                string query = "SELECT [Username], [Password], [AttemptsLeft] FROM [Information] WHERE [Username] = @Username AND [Password] = @Password";

                conn.Open();

                using (SqlCommand cmd = new SqlCommand(query, conn))
                {
                    cmd.Parameters.Add("@Username", SqlDbType.NVarChar);
                    cmd.Parameters["@Username"].Value = Username;

                    cmd.Parameters.Add("@Password", SqlDbType.NVarChar);
                    cmd.Parameters["@Password"].Value = Password;

                    using (SqlDataReader reader = cmd.ExecuteReader())
                    {
                        while (reader.Read())
                        {
                            context.Attempts = Convert.ToInt32(reader["AttemptsLeft"]);
                        }
                    }
                }

                conn.Close();
            }
        }

public bool CheckUser(string Username, string Password)
        {
            using (SqlConnection conn = new SqlConnection(connectionString))
            {
                _query = "SELECT [Username], [Password] FROM [Information] WHERE [Username] = @Username AND [Password] = @Password";

                conn.Open();

                using (SqlCommand cmd = new SqlCommand(_query, conn))
                {
                    cmd.Parameters.Add("@Username", SqlDbType.NVarChar);
                    cmd.Parameters["@Username"].Value = Username;

                    cmd.Parameters.Add("@Password", SqlDbType.NVarChar);
                    cmd.Parameters["@Password"].Value = Password;

                    using (SqlDataReader reader = cmd.ExecuteReader())
                    {
                        if (reader.HasRows)
                        {
                            return true;
                        }

                        else
                        {
                            return false;
                        }
                    }
                }
            }
        }

 public bool CheckStatus(string Username, string Password)
        {
            using (SqlConnection conn = new SqlConnection(connectionString))
            {
                _query = "SELECT [Username], [CurrentStatus] FROM [Information] WHERE [Username] = @Username AND [CurrentStatus] = @CurrentStatus";

                conn.Open();

                using (SqlCommand cmd = new SqlCommand(_query, conn))
                {
                    cmd.Parameters.Add("@Username", SqlDbType.NVarChar);
                    cmd.Parameters["@Username"].Value = Username;

                    cmd.Parameters.Add("@CurrentStatus", SqlDbType.NVarChar);
                    cmd.Parameters["@CurrentStatus"].Value = "Active";

                    using (SqlDataReader reader = cmd.ExecuteReader())
                    {
                        if (reader.HasRows)
                        {
                            return true;
                        }

                        else
                        {
                            return false;
                        }
                    }
                }
            }
        }

public bool SuspendUser(string Username)
        {
            bool flag = false;

            using (SqlConnection conn = new SqlConnection(connectionString))
            {
                string query = "SELECT [Username], [CurrentStatus] FROM [Information] WHERE [Username] = @Username";

                string _query = "UPDATE [Information] SET [CurrentStatus] = @CurrentStatus WHERE [Username] = @Username";

                conn.Open();

                using (SqlCommand cmd = new SqlCommand(query, conn))
                using (SqlCommand _cmd = new SqlCommand(_query, conn))
                {
                    cmd.Parameters.Add("@Username", SqlDbType.NVarChar);
                    cmd.Parameters["@Username"].Value = Username;

                    _cmd.Parameters.Add("@Username", SqlDbType.NVarChar);
                    _cmd.Parameters["@Username"].Value = Username;

                    _cmd.Parameters.Add("@CurrentStatus", SqlDbType.NVarChar);
                    _cmd.Parameters["@CurrentStatus"].Value = "Suspended";

                    flag = Convert.ToBoolean(_cmd.ExecuteNonQuery());
                }

                conn.Close();
            }

            return flag;
        }

public bool DecreaseAttempts(string Username)
        {
            bool flag = false;

            using (SqlConnection conn = new SqlConnection(connectionString))
            {
                string query = "SELECT [Username], [AttemptsLeft] FROM [Information] WHERE [Username] = @Username";

                string _query = "UPDATE [Information] SET [AttemptsLeft] = [AttemptsLeft] - 1 WHERE [Username] = @Username";

                conn.Open();

                using (SqlCommand cmd = new SqlCommand(query, conn))
                using (SqlCommand _cmd = new SqlCommand(_query, conn))
                {
                    cmd.Parameters.Add("@Username", SqlDbType.NVarChar);
                    cmd.Parameters["@Username"].Value = Username;

                    _cmd.Parameters.Add("@Username", SqlDbType.NVarChar);
                    _cmd.Parameters["@Username"].Value = Username;

                    flag = Convert.ToBoolean(_cmd.ExecuteNonQuery());
                }

                conn.Close();
            }

            return flag;
        }

LoginContext 模型:

public int Attempts
        {
            get;
            set;
        }

Controller :

UserManager manager = new UserManager();

[HttpGet]
        public ActionResult Login()
        {
            return View();
        }

[HttpPost]
        public ActionResult Login(LoginContext user, string Username, string Password)
        {
            if (!Request.IsAuthenticated)
            {
                if (ModelState.IsValid)
                {
                    // `if` statement below will gets executed when I enter the password and username correctly.
                    if (manager.CheckUser(Username, Password))
                    {
                        // `if` statement below will gets executed when user's status is not `Suspended`
                        if (manager.CheckStatus(Username, Password))
                        {
                            manager.GetAttempts(Username, Password);

                            // `if` statement below will not gets executed. It is like `user.Attempts` always be `0`.
                            if (user.Attempts > 0)
                            {
                                 FormsAuthentication.SetAuthCookie(user.Username, false);

                                 return RedirectToAction("List", "Home");
                            }

                            //`else` statement below will gets executed whenever I enter the username and password correctly.
                            else
                            {
                                ModelState.AddModelError(string.Empty, "The account: " + Username + ", has been locked due too many failed login attempts!");

                                manager.SuspendUser(Username);
                            }
                        }

                        //`else` statement below will gets executed when user's status is `Suspended`.
                        else
                        {
                            ModelState.AddModelError(string.Empty, "Your account has been locked due too many failed login attempts!");
                        }
                    }

                    // `else` statement below will gets executed when I enter the password wrongly and username correctly or vice versa.
                    else
                    {
                        ModelState.AddModelError(string.Empty, "Username or password incorrect!");

                        manager.DecreaseAttempts(Username);
                    }
                }

                return View(user);
            }

        }

最佳答案

如果您遵循您的方案,当用户无效时,它只会减少您的无效尝试。除非用户正确输入登录凭据,否则它实际上不会暂停用户。

实际上你的 sudo 逻辑应该是:

  1. 验证凭据
    1. 凭证有效
      1. 检查帐户状态,已暂停然后拒绝访问
      2. 将尝试次数重置回 5
      3. 允许用户进入应用程序
    2. 凭据无效
      1. 将尝试次数减少一次
      2. 根据需要将帐户设置为暂停

很多这样的逻辑实际上可以绑定(bind)到一两个方法中。另外,在您的 DecreaseAttempts() 方法中,您有两个 SQL 命令,其中 sql 命令 cmd 永远不会执行

下面是一个使用返回状态枚举的方法执行此操作的示例。这是一个非常基本的示例,但只需要一种方法来执行完整的授权方法。我已经评论了代码。

public partial class UserManager 
{

    const int MaxAttempts = 5;

    public LoginStatus ValidateUser(string username, string password)
    {
        if (string.IsNullOrWhiteSpace(username))
            throw new ArgumentNullException("username");

        //set the password to empty if it is null
        password = password ?? "";

        //create the connection
        using (var connection = new SqlConnection(Configuration.ConnectionString))
        {
            //assign some local variables
            int attemptsLeft = MaxAttempts;
            string currentStatus = "Active";
            string userPassword = null;

            //get the information for the user, only query by username so we have all the data. We will match the password later on
            string query = "SELECT TOP(1) [Username], [Password], [AttemptsLeft], [CurrentStatus] FROM [Information] WHERE Username = @username";

            using (var command = new SqlCommand(query, connection))
            {
                command.Parameters.AddWithValue("@username", username);
                command.CommandType = System.Data.CommandType.Text;

                connection.Open();

                using (var reader = command.ExecuteReader())
                {
                    //no rows.. Invalid username
                    if (!reader.HasRows)
                    {
                        connection.Close();
                        return LoginStatus.InvalidCredentials;
                    }

                    //read the first row (hence the break)
                    while (reader.Read())
                    {
                        attemptsLeft = (int)reader["AttemptsLeft"];
                        currentStatus = (string)reader["CurrentStatus"];
                        userPassword = (string)reader["Password"];
                        break;
                    }
                    reader.Close();
                }
                connection.Close();
            }

            //if the account is suspended then dont even bother with password checking
            if (currentStatus.Equals("Suspended", StringComparison.CurrentCultureIgnoreCase))
            {
                return LoginStatus.Suspended;
            }

            //invalid password lets handle the invalid credentials logic
            if (!password.Equals(userPassword))
            {
                attemptsLeft -= 1;

                //decrease the attempts, lets just stop at zero as we dont need negative attempts
                if(attemptsLeft >= 0)
                {
                    query = "UPDATE [Information] SET [AttemptsLeft] = @attemptsLeft WHERE Username = @username";
                    using (var command = new SqlCommand(query, connection))
                    {
                        command.Parameters.AddWithValue("@username", username);
                        command.Parameters.AddWithValue("@attemptsLeft", attemptsLeft);
                        connection.Open();
                        command.ExecuteNonQuery();
                        connection.Close();
                    }
                }

                //suspend the account when attempts less than or equal to zero
                if (attemptsLeft <= 0)
                {
                    query = "UPDATE [Information] SET [CurrentStatus] = @currentStatus WHERE Username = @username";
                    using (var command = new SqlCommand(query, connection))
                    {
                        command.Parameters.AddWithValue("@username", username);
                        command.Parameters.AddWithValue("@currentStatus", "Suspended");
                        connection.Open();
                        command.ExecuteNonQuery();
                        connection.Close();
                    }
                    //exit method as login account suspended
                    return LoginStatus.Suspended;
                }

                //exit as invalid login credentials
                return LoginStatus.InvalidCredentials;
            }
            //if we are here lets quickly reset the login attempts back to 5, and account status to active as this is a valid login
            query = "UPDATE [Information] SET [AttemptsLeft] = @attemptsLeft, [CurrentStatus] = @currentStatus WHERE Username = @username";
            using (var command = new SqlCommand(query, connection))
            {
                command.Parameters.AddWithValue("@username", username);
                command.Parameters.AddWithValue("@attemptsLeft", MaxAttempts);
                command.Parameters.AddWithValue("@currentStatus", "Active");
                connection.Open();
                command.ExecuteNonQuery();
                connection.Close();
            }
            //if we got here then every thing is a match
            return LoginStatus.Authorized;
        }
    }

}

public enum LoginStatus
{
    Authorized,
    InvalidCredentials,
    Suspended
}

使用它可以像下面一样简单(注意你必须更改 View 重定向)

[HttpPost]
public ActionResult Index(string username, string password)
{
    if(string.IsNullOrWhiteSpace(username))
    {
        this.ModelState.AddModelError("", "Invalid Login Credential. No username sent.");
        return View();
    }

    var manager = new UserManager();

    var result = manager.ValidateUser(username, password);

    switch (result)
    {
        case LoginStatus.Authorized:
            return RedirectToAction("About", "Home");

        case LoginStatus.InvalidCredentials:
            this.ModelState.AddModelError("", "Invalid Login Credentials. Username or password incorrect");
            break;

        case LoginStatus.Suspended:
            this.ModelState.AddModelError("", "Account Suspeneded");
            break;
    }

    return View();
}

只是为了好玩,我将其重写为一个简单的存储过程。

CREATE PROCEDURE ValidateUser
    @username nvarchar(50),
    @password nvarchar(50)
AS
BEGIN

    SET NOCOUNT ON;

    DECLARE @userPassword nvarchar(50) = NULL
    DECLARE @maxAttempts int  = 5
    DECLARE @attemptsLeft int = 5
    DECLARE @currentStatus nvarchar(50)

    /*
        RETURN CODES:
        0 = Authorized
        1 = InvalidCredentials
        2 = Suspended
    */


    SELECT TOP(1) @userPassword = [UserName], @attemptsLeft = [AttemptsLeft], @currentStatus = [CurrentStatus] FROM [Information] WHERE UserName = @username

    IF @userPassword IS NULL
        BEGIN
            SELECT 1 as [Result], @maxAttempts as [AttemptsRemaining]
            RETURN
        END

    --account suspended.. Return a suspended result
    If @currentStatus = 'Suspended'
        BEGIN
            SELECT 2 as [Result], 0 as [AttemptsRemaining]
            RETURN
        END

    --passwords dont match (note this is case insensitive on default collation)
    If @password IS NULL OR @password <> @userPassword
        BEGIN
            --decrease attempts
            SET @attemptsLeft = @attemptsLeft - 1

            --if the attempts left are greater than 0 then set the account active and decrease the attempts remaining
            IF @attemptsLeft > 0
                BEGIN
                    UPDATE [Information] SET [CurrentStatus] = 'Active', AttemptsLeft = @attemptsLeft WHERE UserName = @username
                    SELECT 1 as [Result], @attemptsLeft as [AttemptsRemaining]
                    RETURN
                END
            --else the attempts left are less than or equal to zero therefore they should be suspended and attempts left set to zero (dont want negative attempts)
            ELSE
                BEGIN
                    UPDATE [Information] SET [CurrentStatus] = 'Suspended', AttemptsLeft = 0 WHERE UserName = @username
                    SELECT 2 as [Result], 0 as [AttemptsRemaining]
                    RETURN
                END
        END
    --if we get here then all is good and we can just reset the account status and max attempts for the next login attempt
    UPDATE [Information] SET [CurrentStatus] = 'Active', AttemptsLeft = @maxAttempts WHERE UserName = @username
    SELECT 0 as [Result], @maxAttempts AS [AttemptsRemaining]

END
GO

然后调用它非常简单(注意我还将返回类型更改为返回状态和剩余尝试次数的调用。

方法

public LoginResult ValidateUserStoredProcedure(string username, string password)
{
    if (string.IsNullOrWhiteSpace(username))
        throw new ArgumentNullException("username");

    //set the password to empty if it is null
    password = password ?? "";

    //create the connection
    using (var connection = new SqlConnection(Configuration.ConnectionString))
    {
        var result = new LoginResult
        {
            AttemptsRemaining = 5,
            Status = LoginStatus.InvalidCredentials
        };
        try
        {
            using (var command = new SqlCommand("EXEC ValidateUser @username, @password", connection))
            {
                command.Parameters.AddWithValue("@username", username);
                command.Parameters.AddWithValue("@password", password);
                command.CommandType = System.Data.CommandType.Text;
                connection.Open();
                using (var reader = command.ExecuteReader())
                {
                    while (reader.Read())
                    {
                        result.Status = ((LoginStatus)(int)reader["Result"]);
                        result.AttemptsRemaining = (int)reader["AttemptsRemaining"];
                        break;
                    }
                    reader.Close();
                }
                connection.Close();
            }
            return result;
        }
        catch (Exception ex)
        {
            if (connection.State != System.Data.ConnectionState.Closed)
                connection.Close();

            Debug.WriteLine("Error on sql query:" + ex.Message);
            return result;
        }
    }
}

结果类

public class LoginResult
{
    public LoginStatus Status { get; set; }

    public int AttemptsRemaining { get; set; }
}

public enum LoginStatus : int
{
    Authorized = 0,
    InvalidCredentials = 1,
    Suspended = 2
}

Controller

[HttpPost]
public ActionResult Index(string username, string password)
{
    if (string.IsNullOrWhiteSpace(username))
    {
        this.ModelState.AddModelError("", "Invalid Login Credential. No username sent.");
        return View();
    }
    var manager = new UserManager();
    var result = manager.ValidateUserStoredProcedure(username, password);
    switch (result.Status)
    {
        case LoginStatus.Authorized:
            return RedirectToAction("About", "Home");

        case LoginStatus.InvalidCredentials:
            if (result.AttemptsRemaining < 5)
                this.ModelState.AddModelError("", "Invalid Login Credentials. Username or password incorrect. Attempts remaining:" + result.AttemptsRemaining);
            else
                this.ModelState.AddModelError("", "Invalid Login Credentials. Username or password incorrect.");
            break;

        case LoginStatus.Suspended:
            this.ModelState.AddModelError("", "Account Suspeneded");
            break;
    }
    return View();
}

如何优化取决于您,但这种授权级别相当弱。它还表明您正在将密码存储为纯文本。但这是另一个话题了。

关于c# - 如果 5 次尝试失败,则锁定用户,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30928355/

相关文章:

c# - 从 ListView 中选择索引

c# - 如何在 Visual Studio 2010 中测试函数?

c# - 您会购买哪本 C# 4.0 书籍,为什么?

sql - 如何过滤 Django 模型以仅包含出现在子表(带有外键)中的模型?

c# - ASP.NET MVC 仅缺少一个 Controller 的默认操作

c# Windows form应用程序窗体问题

sql - 爆炸后如何从数组中删除一个元素

javascript - asp.net mvc,以 JSON 形式返回多个 View

asp.net-mvc - 无法使用formdata Asp.net-mvc上传图片

sql - 查询成本最佳实践