【问题标题】:How to lock user account for some period of time?如何锁定用户帐户一段时间?
【发布时间】:2018-03-19 19:20:35
【问题描述】:

我想知道如何以及在 X 次登录失败后锁定用户帐户的最佳方法是什么?我有一个表,我可以在其中跟踪用户失败的登录尝试。表存储时间戳、用户名、IP 地址和浏览器类型。在我检测到不正确的登录信息后,cfquery 将根据用户名或 IP 地址从失败的登录表中提取记录。如果有 5 次或更多无效尝试,我将帐户设置为不活动。现在我想以某种方式设置计时器,该计时器将在该用户上次无效尝试后开始计数 5 分钟。然后帐户应将状态更改为活动。这是我到目前为止的代码:

<cfquery name="checkUser" datasource="#dsn#">
    SELECT UserName, Password, Salt, LockedUntil
    FROM Users
    WHERE UserName = <cfqueryparam cfsqltype="cf_sql_varchar" value="#trim(FORM.username)#" maxlength="50">
       AND Active = 1
</cfquery>

<cfif len(checkUser.LockedUntil) AND dateCompare(now(), checkUser.LockedUntil,'n') EQ -1>
    <cfset fnResults.status = "400">
    <cfset fnResults.message = "This account is locked for 5 min.">
    <cfreturn fnResults>
    <cfabort>
</cfif>

<cfset storedPW = checkUser.Password>
<cfset enteredPW = FORM.password & checkUser.Salt>

<cfif checkUser.recordCount NEQ '1' OR (hash(enteredPW,"SHA-512") NEQ storedPW>
    <cfquery name="logFail" datasource="#dsn#">
        INSERT INTO FailedLogins(
           LoginTime,
           LoginUN,
           LoginIP,
           LoginBrowser
        )VALUES(
           CURRENT_TIMESTAMP,
           <cfqueryparam cfsqltype="cf_sql_varchar" value="#FORM.username#" maxlength="50">,
           <cfqueryparam cfsqltype="cf_sql_varchar" value="#REMOTE_ADDR#" maxlength="20">,
           <cfqueryparam cfsqltype="cf_sql_varchar" value="#CGI.HTTP_USER_AGENT#" maxlength="500">
        )
    </cfquery>

    <!--- Pull failed logins based on username or IP address. --->
    <cfquery name="failedAttempts" datasource="#dsn#">
        SELECT LoginTime
        FROM FailedLogins
        WHERE LoginUN = <cfqueryparam cfsqltype="cf_sql_varchar" value="#trim(FORM.username)#" maxlength="50">
            OR LoginIP = <cfqueryparam cfsqltype="cf_sql_varchar" value="#REMOTE_ADDR#" maxlength="20">
    </cfquery>

    <cfif failedAttempts.recordcount LT 4>
        <cfset fnResults.status = "400">
        <cfset fnResults.message = "Invalid Username or Password!">
    <cfelseif failedAttempts.recordcount EQ 4>
        <cfset fnResults.status = "400">
        <cfset fnResults.message = "This is your last attempt. If you fail to provide correct information account will be locked!">
    <cfelseif failedAttempts.recordcount GTE 5>
        <cfset lockUntil = DateAdd('n', 5, now())>
        <cfquery name="blockUser" datasource="#dsn#">
            UPDATE Users
            SET LockedUntil = <cfqueryparam cfsqltype="cf_sql_timestamp" value="#lockUntil#">
            WHERE UserName = <cfqueryparam cfsqltype="cf_sql_varchar" value="#trim(FORM.username)#" maxlength="50">
        </cfquery>

        <cfset fnResults.status = "400">
        <cfset fnResults.message = "This account is locked for 5 min.">
    </cfif>
<cfelse>
   //Clear failed login attempts
   //Update lockedUntil field to NULL
   //User logged in authentication successful!
</cfif>

帐户设置为非活动/锁定后,设置倒计时和更改标志状态的最佳方法是什么?我看到有人推荐 SQL Job,但我不确定作业应该多久运行一次以及如何创建该语句?如果有人可以提供一些例子,请告诉我。谢谢你。

【问题讨论】:

  • 推荐的 SQL 作业,但我不确定...如何创建该语句请不要采取错误的方式,但您是否查看过您的 dbms 文档并实际尝试了这些例子?搜索“sql server 计划作业”会返回大量结果和示例。 stackoverflow.com/questions/5471080/….
  • @Ageax 我已经检查过了,是的,有很多示例,但对于我必须在特定时间段内锁定记录的这种特殊情况却没有。另外我不确定这是否是最有效的方法,因为 SQL 作业应该一直在运行,并不断检查失败的尝试记录。
  • 不要创建计划作业。这不是必需的,因此会给您的数据库添加不需要的处理。看看@David-Faber 提供的答案。只需在您的查询中添加一个额外的检查,以查看当前的登录尝试是否超出了锁定期。
  • @Shawn 或者您在/如果成功登录尝试时进行清理。
  • 我的意思是,在用户成功输入登录凭据(而不是计划的作业)后清理数据库。仅适用于该用户。没关系,无论哪种方式都可以。

标签: sql sql-server-2008 coldfusion locking unlock


【解决方案1】:

您可以在checkUser 查询中添加一个条件:

<cfquery name="checkUser" datasource="#dsn#">
    SELECT UserName, Password, Salt, Active
      FROM Users u
     WHERE UserName = <cfqueryparam cfsqltype="cf_sql_varchar" value="#trim(FORM.username)#" maxlength="50">
      -- AND Active = 1
      AND NOT EXISTS ( SELECT 1 FROM FailedLogins fl
                        WHERE fl.LoginUN = u.UserName
                          AND DATEDIFF('ss', fl.LoginTime, CURRENT_TIMESTAMP) >= 300 )
</cfquery>

我使用300 秒而不是5 分钟,因为我相信DATEDIFF() 返回一个int。如果这不是 SQL Server 的理想语法,我提前道歉(我不经常使用它)。

然后,在上面,如果Active 为零,那么您可以(假设密码正确)将其更新为值1 并删除与该帐户关联的失败登录或以某种方式将它们标记为非活动状态,以便它们不要再计入 5 次失败的登录。

根据以下评论者的建议修改了查询:(顺便说一句,好建议!)

<cfquery name="checkUser" datasource="#dsn#">
    SELECT UserName, Password, Salt, Active
      FROM Users u
     WHERE UserName = <cfqueryparam cfsqltype="cf_sql_varchar" value="#trim(FORM.username)#" maxlength="50">
      -- AND Active = 1
      AND NOT EXISTS ( SELECT 1 FROM FailedLogins fl
                        WHERE fl.LoginUN = u.UserName
                          AND fl.loginTime < DATEADD(second, -300, CURRENT_TIMESTAMP) )
</cfquery>

【讨论】:

  • 小建议。计算所需的时间戳并使用col &gt;= {value}col &lt;= {value} 创建比较。它比 dateDiff() 对性能和索引更友好。
  • DATEDIFF() 中的 ss 拼写为 second。缩写可以减少打字,但可能会导致一些意想不到的结果。
  • 虽然这对登录非常有效,但您仍然应该运行 SQL 作业来清理您的 FailedLogins 表。否则,如果有人登录失败 5 次然后离开,他们失败的登录将保留在您的数据库中,直到他们下次登录时,可能永远存在。在记录事物时,清理可能非常重要。
  • 我不确定是否要从查询中删除活动标志。这部分有点令人困惑,为什么它被注释掉了?
  • their 不是 they're
【解决方案2】:

我认为你会更好地扭转你的逻辑。 不要让列 status 具有值 ActiveInactive,而是考虑使用列 locked_until 时间。

最初,新用户的locked_until 值为NULL(或0),表示未锁定。

当出现一系列登录失败时,设置为当前时间+5分钟。

对于此用户的所有操作,检查当前时间是否 >locked_until 值。 如果不是,则该帐户仍处于未激活状态(锁定)。

编辑:我决定写一些代码,因为我忘记了用户成功登录的原因。请见下文;我不确定最初的问题是什么语言,但这个答案是伪python。

假设我们有一个类似于以下的数据库表(忽略盐等)

CREATE TABLE Users (
    UserName TEXT PRIMARY KEY,
    Password TEXT NOT NULL,
    LockUntil TIMESTAMP,
    FailedLogins INT DEFAULT 0
);

登录检查功能如下所示。 重点是:

  • 成功登录清除设置 FailedLogins 为 0。
  • 锁定帐户时将 FailedLogins 设置为 5(连同 LockUntil)。
  • FailedLogins=5 的新登录失败是对新解锁帐户的尝试。 (即帐户已隐式解锁,用户正在重试)。
def try_login(username, password):
    row = execute("SELECT Password,LockUntil,FailedLogins FROM Users WHERE UserName=?", username);
    if row is None:
        print("Unknown username")
        return False

    if row.LockUntil is not None and current_time() < row.LockUntil:
        print("Account locked. Try again later.")
        return False

    if password == row.Password:
        print('Successful login')
        execute("UPDATE Users SET LockUntil=NULL, FailedLogins=0 WHERE UserName=?", username)
        return True

    if row.FailedLogins == 4:
        print("Too many failures; locking account for 5 mins")
        lock_until = current_time() + 300
        execute("UPDATE Users SET LockUntil=?,FailedLogins=5 WHERE UserName=?", lock_until, username)
        return False

    failures = row.FailedLogins + 1
    if failures == 6:
        # User had locked account, which is now unlocked again.
        # But they failed to login again, so this is failure 1.
        failures = 1
    execute("UPDATE Users SET FailedLogins=? WHERE UserName=?", failures, username)
    return False

【讨论】:

  • 基本上一旦用户发送用户名和密码,我将从checkUser查询中提取locked_until。如果该字段不为 NULL,则检查当前时间是否大于 locked_until,如果不继续登录过程。?
  • 我已经尝试过,locked_until 似乎是一个不错的选择。唯一关心的解决方案是时间到期后会发生什么?如果用户重置密码,他们将成功通过身份验证,所有失败的尝试都将被删除,但如果他们仍然输入错误的密码,那会怎样?计数将上升到 6,所以我想知道在这种情况下下一步应该是什么?
  • 锁定在您放入“locked_until”时过期。这种方法背后的想法是,当您意识到登录失败并且帐户应该被锁定时,您只需要更改数据库中的行。无需稍后再回来说该帐户现在处于活动状态,只需时间已过,因此不再适用锁定。
  • 我的解决方案略有不同,但仍应相同。我已经更新了我上面的问题。我已经测试过并且工作正常。如果您发现任何可以改进或有更好方法的地方,请告诉我。我会接受您的解决方案作为正确答案。谢谢。
猜你喜欢
  • 2020-01-13
  • 2012-01-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-08-02
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多