简体   繁体   中英

MySQL - Return login attempts by intervals

I'm trying to block a user's access after a given number of attempts. Before stop reading because this question has been asked before, let me explain my case (and if it's been already asked, please point out the duplicated question).

What I'd like

What I'd like to implement is a block-time per intervals. So, if there have been:

  • 3 attempts in 5 minutes: user blocks by 5 minutes
  • 5 attempts in 10 minutes: user blocks by 10 minutes
  • 10 attempts in 15 minutes: user blocks by 15 minutes

This takes into account what e-mail has tried to login and from what IP. So, if the user tries to access the same e-mail account from a different IP, it's able to do so (and if the same IP tries another user it's able to do so too).

Please, I know it's not the best solution in security issues - maybe - but solving the problem will help me also in my MySQL learning, so I'd like to skip security recommendations and center in how to achieve this in a MySQL query, if possible.

What I have

I've got a table in my database with Login Attempts:

CREATE TABLE IF NOT EXISTS `er_login_attempts` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `target_mail` varchar(128) COLLATE utf8_unicode_ci NOT NULL,
  `attempt_date` datetime NOT NULL,
  `attempt_success` tinyint(4) DEFAULT '1',
  `ip_from` varchar(36) COLLATE utf8_unicode_ci NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=1 ;

So, I've got a register of the dates of the login attempts. Each attempt creates a new register. This is done this way because of the different intervals (so I cannot use a "attempts" field as I would'nt know how much time has passed since last attempt).

If the attempt finally gets successfull, the "attempt_success" of this entry is set to 1. If not, it's set to 0.

What I've tried

What I've tried to do is to write a MySQL query that returns the number of wrong attempts per intervals. Then, I'd take this in PHP and make some IF's to check if the limit has been reached for each interval.

So, the query I've tried (please, do not laugh):

SELECT count(cinc.id) AS cinc_minuts, count(deu.id) AS deu_minuts, count(quinze.id) AS quinze_minuts 
    FROM er_login_attempts cinc, er_login_attempts deu, er_login_attempts quinze 
    WHERE 'mail@mail.com' IN (cinc.target_mail, deu.target_mail, quinze.target_mail) 
        AND '1.11.11.111' IN (cinc.ip_from, deu.ip_from, quinze.ip_from) 
        AND cinc.attempt_date BETWEEN MAX(cinc.attempt_date) AND DATE_SUB(MAX(cinc.attempt_date), INTERVAL 5 minutes)  
        AND deu.attempt_date BETWEEN MAX(deu.attempt_date) AND DATE_SUB(MAX(deu.attempt_date), INTERVAL 10 minutes)  
        AND quinze.attempt_date BETWEEN MAX(quinze.attempt_date) AND DATE_SUB(MAX(quinze.attempt_date), INTERVAL 5 minutes)

But! I get a "Group By" error. I've tried to add a "Group By" expression to group this by target_mail, by IP, by INTERVALS, and I'm getting the same error again and again.

However, as you can see, this query does not contemplate that a successfull login attempt has been made, and it would be wonderful that if there has been a successfull attempt, all older wrong attempts are ignored - but not deleted.

With all this

I hope you can get an idea of what I'm trying to achieve. Again, if it's been asked before I'd thank those who point me in the right direction and - if it's been so - sorry for repeating the question.

If you need further information or I've skipped something important, please, let me know and I'll try to write down the missing information.

Thanks to everybody for your time!

First thing of note is DO NOT STORE IP ADDRESSES AS STRINGS - they are a convenient way to make large numbers readable by human beings. But if you want to start doing further processing on the address (scan for logons in the subnet, apply country specific validation...) then you need to use the IP number.

SELECT 
  SUM(IF(secs_ago<=300, 1, 0)) as attempts_in_last_5,
  SUM(IF(secs_ago<=600, 1, 0)) as attempts_in_last_10,
  SUM(IF(secs_ago<=900, 1, 0)) as attempts_in_last_15
FROM
(
  SELECT
    UNIX_TIMESTAMP(NOW()) - UNIX_TIMESTAMP(attempt_date) AS secs_ago
  FROM er_login_attempts
  WHERE target_email='mail@mail.com'
  AND INET_ATON('1.11.11.111')=ip_from
  AND attempt_date>NOW() - INTERVAL 15 MINUTES
) ilv;

....and your primary key is pretty much useless for the purpose you have described - but you do need an index on attempt_date, target_email and ip_from (most likely in that order).

This needs some work to accommodate the blocking period - simplest solution would be to add a second table.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM