SecureSocial & Memcached

As I promised earlier there will be some news from battlefield 😉

SecureSocial is a plugin for Play! Framework which provides support for authentication and authorization (through both classic user/pass and OAuth providers).
It requires presence of CacheAPI implementation plugin but don’t worry – Play comes with default implementation based on Ehcache, which works very well.

But we want to try also alternative solution – Memcached, a distributed memory object caching system.
Maciej add play2-memcached plugin to project dependencies and configure it. I also have to install memcached daemon on my local machines.

Application starts without any warnings and everything goes as expected until I fire secured action.
This was a point where the problems begins 😦

Key is empty

When I tried to fire some simple SecuredAction I get an following exception:

java.lang.IllegalArgumentException: Key must contain at least one character.

I look closer to the stacktrace and start my investigation. Key was empty as exception message says, so my first direction was to find how the keys are built.

I quickly discovered that play2-memcached can prepend so called namespace to the given key to allow sharing the same storage between many application instances without key collisions.

In that moment little, yellow light bulb shines above my head 😉 I can provide workaround by setting memcached.namespace  value in application.conf. I was awared that this is only workaround and the problem still lies somwhere in between chair and memcached plugin.

Key is too long

That works perfectly – when I wasn’t authenticated SecureSocial redirects me to a appropriate login page.
When I was going to register a new user everything goes right until I commit second registration step (filling the user details form after receive e-mail with activation link).

I got IllegalArgumentException one more time, but with different cause:

java.lang.IllegalArgumentException: Key is too long (maxlen = 250)

At this moment I think something like “if this would be a human being then it will be a very disgruntled woman” 😉

As before I started my investigation from a top of stacktrace:


java.lang.IllegalArgumentException: Key is too long (maxlen = 250)
at net.spy.memcached.util.StringUtils.validateKey(StringUtils.java:69) ~[spymemcached-2.8.9.jar:2.8.9]
at net.spy.memcached.MemcachedConnection.enqueueOperation(MemcachedConnection.java:640) ~[spymemcached-2.8.9.jar:2.8.9]
at net.spy.memcached.MemcachedClient.asyncStore(MemcachedClient.java:301) ~[spymemcached-2.8.9.jar:2.8.9]
at net.spy.memcached.MemcachedClient.set(MemcachedClient.java:704) ~[spymemcached-2.8.9.jar:2.8.9]
at com.github.mumoshu.play2.memcached.MemcachedPlugin$$anon$2.set(MemcachedPlugin.scala:129) ~[play2-memcached_2.10-0.3.0.2.jar:0.3.0.2]
at play.api.cache.Cache$.set(Cache.scala:56) ~[play_2.10-2.1.3.jar:2.1.3]

view raw

stackTrace

hosted with ❤ by GitHub

Unfortunately I can’t find cause in any of these method calls.

At the same moment I remind myself that few days ago I was looking for cause of logging off when Play! is reloaded after some code changes in dev mode and I found answer on stackoverflow.
Jorge explained there that AuthenticatorStore’s default implementation persists user information using Play!’s cache abstraction.

I study its source code, especially DefaultAuthenticatorStore and I discovered two important things:

  1. An authenticator id is used as a cache key. Id is generated by IdGenerator trait implementation,
  2. Method find never checks that given key is empty, it always forwards them to the cache plugin. That was a cause of my first problem

I also found that DefaultIdGenerator produces 256 characters long identifiers – it is too long for Memcached.

My solution

Update: Solution described below is outdated. I succeed with providing a more straightforward solution, which I described in “SecureSocial & Memcached – Never that easy”.

I create my custom implementations of IdGenerator and AuthenticatorStore which restricts the invariants:


package service.auth
import securesocial.core.IdGenerator
import play.api.Application
import java.security.SecureRandom
import play.api.libs.Codecs
class MemcachedIdGenerator(app: Application) extends IdGenerator(app) {
val random = new SecureRandom()
val memcachedNsLen = app.configuration.getString("memcached.namespace").map(_.length).getOrElse(0)
val idSizeInBytes = 125 – (memcachedNsLen / 2) – (memcachedNsLen % 2)
/**
* Generates a new id using SecureRandom
*
* @return the generated id
*/
def generate: String = {
val randomValue = new Array[Byte](idSizeInBytes)
random.nextBytes(randomValue)
Codecs.toHexString(randomValue)
}
}


package service.auth
import securesocial.core.{Authenticator, DefaultAuthenticatorStore}
import play.api.Application
class MemcachedAuthenticatorStore(app: Application) extends DefaultAuthenticatorStore(app) {
override def find(id: String): Either[Error, Option[Authenticator]] =
if (id.isEmpty)
Right(None)
else
super.find(id)
}

I replace problematic implementations in play.plugins with these new ones and now everything works as expected 🙂

Also memcached namespace isn’t needed any more to avoid empty key problem.

Conclusion

There is a lot of joy in such investigations (I fixed the problem, oh yeah!).
I decide to make a pull request to SecureSocial repository which will provides memcache support “out of the box”.

Question

What do you think – which element should be responsible for checking if given key is empty when covered implementation doesn’t support such keys?

Does client (secure social) have to do that or maybe the middleware (memcached play plugin) in between framework abstraction and concrete 3rd party solution?

Don’t hesitate yourself to discuss in comments 🙂

5 thoughts on “SecureSocial & Memcached

  1. Thanks for your workaround. I faced the same problem today and came across the same solution for a long-key-problem. I’m quite sure that securesocial plugin is a wrong place for a fix with an empty-key-problem, while an empty key is a general case. Its not possible to use an empty key in memcached, so the memcached plugin should return None on quering it with empty key. I’ve reported an issue: https://github.com/mumoshu/play2-memcached/issues/25

  2. Pingback: SecureScial & Memcached – never that easy! | Dev's Pad

Leave a comment