4

I have a questions regarding synchronization of HashMap. The background is that I am trying to implement a simple way of Brute-Force-Detection. I will use a map which has username as key and is used to save the amount of failed login attempts of the user. If a login fails, I want to do something like this:

    Integer failedAmount = myMap.get("username");
    if (failedAmount == null) {
        myMap.put("username", 1);
    } else {
        failedAmount++;
        if (failedAmount >= THRESHOLD) {
            // possible brute force detected! alert admin / slow down login
            // / or whatever
        }
        myMap.put("username", failedAmount);
    }

The mechanism I have in mind at the moment is pretty simple: I would just track this for the whole day and clear() the HashMap at midnight or something like that.

so my question is: what is the best/fastest Map implementation I can use for this? Do I need a fully schronized Map (Collections.sychronizedMap()) or is a ConcurrentHashMap sufficient? Or maybe even just a normal HashMap? I guess it's not that much of a problem if a few increments slipped through?

BenMorel
  • 34,448
  • 50
  • 182
  • 322
Mario B
  • 2,102
  • 2
  • 29
  • 41

5 Answers5

5

I would use a combination of ConcurrentHashMap and AtomicInteger http://docs.oracle.com/javase/6/docs/api/java/util/concurrent/atomic/AtomicInteger.html.

Using AtomicInteger will not help you with the comparison, but it will help you with keeping numbers accurate - no need to doing the ++ and the put in two steps.

On the ConcurrentHashMap, I would use the putIfAbsent method, which will eliminate your first if condition.

AtomicInteger failedAmount = new AtomicInteger(0);

failedAmount = myMap.putIfAbsent("username", failedAmount);

if (failedAmount.incrementAndGet() >= THRESHOLD) {
    // possible brute force detected! alert admin / slow down login
    // / or whatever
}
nwinkler
  • 52,665
  • 21
  • 154
  • 168
3

Unless you synchronize the whole block of code that does the update, it won't work as you expect anyway.

A synchronized map just makes sure that nothing nasty happens if you call, say, put several times simultaneously. It doesn't make sure that

myMap.put("username", myMap.get("username") + 1);

is executed atomically.

You should really synchronize the whole block that performs the update. Either by using some Semaphore or by using the synchronized keyword. For example:

final Object lock = new Object();

...

synchronized(lock) {

    if (!myMap.containsKey(username))
        myMap.put(username, 0);

    myMap.put(username, myMap.get(username) + 1);

    if (myMap.get(username) >= THRESHOLD) {
        // possible brute force detected! alert admin / slow down login
    }
}
aioobe
  • 413,195
  • 112
  • 811
  • 826
2

The best way I see would be to store the fail counter with the user object, not in some kind of global map. That way the synchronization issue does not even turn up.

If you still want to go with the map you can get aways with a partially synchronized approach, if you use a mutable counter object:

static class FailCount {
    public int count;
}

// increment counter for user
FailCount count;
synchronized (lock) {
    count = theMap.get(user);
    if (count == null) {
        count = new FailCount();
        theMap.put(user, count);
    }
}
count.count++;

But most likely any optimization attempt here is a waste of time. Its not like your system will process millions of login failures a second, so your orginal code should do just fine.

Durandal
  • 19,919
  • 4
  • 36
  • 70
1

The simplest solution I see here is to extract this code into a separate function and make in synchronized. (or put all your code into a synchronized block). All other left unchanged. Map variable should be made final.

dhblah
  • 9,751
  • 12
  • 56
  • 92
1

Using a synchronized HashMap or a ConcurrentHashMap is only necessary if your monitoring application is multi-threaded. If that is the case, ConcurrentHashMap has significantly better performance under cases of high load/contention.

I would not dare use an unsynchronized structure with multiple threads if even one writer/updater thread exists. It's not just a matter of losing a few increments - the internal structure of the HashMap itself could be corrupted.

That said, if you want to ensure that no increment is lost, then even a synchronized Map is not enough:

  • UserX attempts to login
  • Thread A gets count N for "UserX"
  • UserX attempts to login again
  • Thread B gets count N for "UserX"
  • A puts N + 1 to the map
  • B puts N + 1 to the map
  • The map now contains N + 1 instead of N + 2

To avoid this, either use a synchronized block for the whole get/set operation or use something along the lines of an AtomicInterer instead of a plain Integer for your counter.

thkala
  • 84,049
  • 23
  • 157
  • 201