2015-06-19 112 views
2

我已經可以減少數據庫中的AttemptsLeft設置爲5。如果我用正確的用戶名輸入密碼錯誤,AttemptsLeft將減少1。如果用戶的狀態是Suspended,我也已經可以讓用戶不登錄系統。鎖定用戶,如果失敗5次嘗試

問題:

我定當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; 
     } 

控制器:

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); 
      } 

     } 
+3

或者您可以使用內置的aspnet_Membership提供程序,爲您完成所有這些工作。 –

+0

GetAttempts()創建並填充局部變量,然後永遠不會返回它,所以,我不確定你想在那裏完成什麼。 –

+0

@JK:我試圖不依賴於「ASP.Net Membership」並試圖讓它自己完成。 :p – Stainn

回答

2

如果您按照您的方案,當用戶無效它只decresases您的無效嘗試。它不會實際掛起用戶,除非他們正確輸入登錄憑證。

真的是你的sudo的邏輯應該去:

  1. 驗證憑據
    1. 證書有效
      1. 檢查帳戶狀態,被暫停則拒絕訪問
      2. 重置嘗試回到5
      3. 允許用戶進入應用程序
    2. 證書無效
      1. 由一個
      2. 帳戶減少的企圖設置爲如果需要的話

此邏輯的潤通可以真正被綁定到一個或兩個方法暫停。同樣在你的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 
} 

要使用這可能是如下那樣簡單(注意,您必須更改視圖重定向)

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

控制器

[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(); 
} 

您如何進行優化取決於您,但這種授權級別相當薄弱。它還顯示您將密碼存儲爲純文本。但這是另一個話題。

+0

是的,我知道一個,'登錄'代碼如此混亂,它不是正確的程序,太多'if語句和太多的檢查 - 「。對於'DecreaseAttempts()'方法,我想在更新之前 – Stainn

+0

我已更新爲使用多個內嵌查詢和示例存儲過程方法顯示代碼示例。 – Nico

+0

謝謝。它正常工作!:DI剛剛打開了我的堆棧溢出帳戶,很抱歉。 – Stainn

相關問題