Making micro-services/REST-API/WebApps stateless, secure and deploy in any elastic private cloud or docker container clusters using Spring & Memcached
The problem: Building rest API using Spring Framework, is cool and newer versions of Spring and Spring Boot Framework made it easier than ever before. But how about moving such an application into elastic cloud and deploy as many of them, as needed basis without worrying about managing clusters, persistent session, server affinity by using load balancer etc. It can still be achieved by session replication. But if you want dynamic scaling, session replication has still a lot of headache to deal with. We also want to keep the individual applications secure by user authentication and authorization. Yes, basic-authentication can be used. But what if, we want to support both basic-authentication and Cookie based one time login?
The idea came to our (not only mine) mind that, what if we can make the application itself completely HTTPSession independent, but also secure, and store everything outside J2EE container, including user’s security context generated by Spring AuthenticationManager, when users login for the first time.
Solution: The solution proposed here (and implemented) needed an external storage to get rid of in-server HTTPSession (part of J2EE specification). Preferably a very fast and memory based storage with capability of failover. Doing little research over the internet, Memcached (https://memcached.org/) seemed to be an ideal one. It is open-source, fast and simple to use. Also can be clustered for failover and load balancing.
Step-1: Find a Java API to communicate with Memcached.
This was simple, I suggest the easiest one to use is, simple-spring-memcached (https://github.com/ragnor/simple-spring-memcached/). It’s MIT Licensed, source code available in Github, open to modifications, have minimum dependencies, annotation driven and easy to configure (just needs memcache host and port). The only problem was it did have store, fetch functionality but no touch. I had to add that. Why we need touch functionality? will discuss later.
Step-2: Tweak Spring Framework to store SecurityContext into Memcached.
First, discussing normal spring-web-framework version 4.0.5 and above. Will discuss spring boot later. What is expected at this step is that, let everything spring-web-framework do. Including intercepting all http requests using normal chain of filters by FilterSecurityInterceptor, ExceptionTranslationFilter and all other authentication filters.
The filter chain looked as below:
Additional security related filters like CORS or CSRF is always good practice. But below example does not include them for simplicity purpose.
All filters are default spring provided filters by their name, except one. SecurityContextPersistenceFilter, We needed to override this one to persist security context to Memcached. It’s easy, just check spring’s implementation of this filter to store security context in session. Instead of session we need to override SecurityContextRepository class and provide a Memcached implementation of SecurityContextRepository.
Define securityContextPersistenceFilter bean in applicationContext-security.xml.
Replace HttpSessionSecurityContext by your implementation of CachedSecurityContextRepository. Coding the implementation is simple. Override loadContext(), saveContext(), containsContext() by getting, setting and validating context to and from Memcached.
Note: Why we need “touch” functionality?
When user first logged in, the security context is created for the first time, and stored with the key of the generated authentication token. The auth token value passed to the caller as a cookie. Subsequent requests containing this cookie and a valid token inside it, is used to retrieve security-context from Memcached. If the security-context is successfully retrieved, that means the user logged in and authenticated before. But this time, we need to extend the caching time of the security-context. Not re-store it back to reduce network traffic. Hence a touch operation is better and needs to be performed to extend the longevity of the security context in Memcached. This just provides the inactive login timeout, same as session timeout setting in application servers.
No matter how many instances of the same application or different applications running, following the same principle of retrieving security-context from Memcached, it will work. On logout just remove the cookie and the corresponding security-context from Memcached.
Code sample:
Note: In the loadContext we can do other additional checks - like custom headers/key or original IP matching or not, with current request IP, signaling an attempt of session hijack etc..
Note: Another piece of advice...
Do not store Spring SecurityContext directly into memcache, use a DTO (Data Transfer Object) instead.
This enabled us to deploy any number of instances of our J2EE based REST API application at a given time (dynamically scaled based on demand). By distributing incoming requests to least-loaded-first fashion, without over loading any particular instance. This approach freed us from server/session affinity, eliminates the use of session cookie and make our application truly stateless. Just remember DO NOT store anything in session while serving your REST API. And make sure the Memcached server/cluster within a firewall have failover capability (at least 2 instances).
Next, I will discuss doing the same using spring boot applications with embedded Tomcat application server. It’s easier than using conventional Spring framework.
Web application state is not security state alone alone. In many cases removing dependency on security state/context is the smallest (in size) and easiest thing to do. Unless dependency on in memory application state (beyond things like authentication) is also addressed, applications cannot be state less. e.g. User specific data, result sets etc
I can see that you still love what you do Amitava! Good step by step instruction here for building REST- API! Also helps those of us not as technical to better conceptualize REST-API/WebApps