Как заблокировать учетную запись пользователя на какой-то период времени?

Мне интересно, как и как лучше всего заблокировать учетную запись пользователя после 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, но я не уверен, как часто должно выполняться задание и как создать это утверждение? Если кто-нибудь может привести пример, пожалуйста, дайте мне знать. Спасибо.

2 ответа

Решение

Я думаю, вам повезет с изменением вашей логики. Вместо того, чтобы столбец status со значениями Active или же Inactiveрассмотреть вопрос о наличии столбца locked_until время вместо

Первоначально значение locked_until для нового пользователя будет равно NULL (или 0), что означает, что оно не заблокировано.

При наличии серии неудачных входов установите текущее время + 5 минут.

Для всех действий для этого пользователя проверьте, является ли текущее время значением> locked_until. Если нет, то учетная запись все еще деактивирована (заблокирована).

Изменить: Я решил написать некоторый код, потому что я забыл учесть пользователей, успешно вошедших в систему. Пожалуйста, смотрите ниже; Я не уверен, на каком языке исходный вопрос, но этот ответ - псевдопифон.

Предполагая, что у нас есть таблица базы данных, подобная следующей (игнорирование солей и т.д..)

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 запрос:

<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>
Другие вопросы по тегам