Spring Framework’s Production Grade Security Module

The Spring Framework provides Java developers with abstractions for most essential parts of a system – Spring Batch for batch processing, Spring Scheduling to take care of scheduled tasks etc.

I worked with the module that deals with security recently and have been pleasantly surprised with the ease of adapting it to one’s use case and the inherent flexibility of the system.

Before delving into Spring Security itself, one must think of what is expected of a production grade security module (or what I expected of one I’d use) :

  1. Authentication with more than one type of input. Say I want to pass a JWT token, or the actual username and encrypted password or maybe I could have a secret passcode based entry into my system. All of these modes, all at once.
  2. All of this functionality should lie separately and not be interleaved with my business logic.
  3. Another developer should not have to know the ins and outs of the security framework while adding a non-security related feature.
  4. Extensibility within a micro-service architecture.
  5. Minimal datastore calls. I don’t want to add an additional call to every API that’s in the system.*
  6. Most importantly, this should be the first entry point into a system and no calls should be made that bypass this – unless specifically desired! For eg. new logins to your system should be possible even without a previously signed JWT token or a registered username/password.

Spring Security gets to work immediately. The first time you run your application, Spring Security has already set up a bunch of filters that will serve as the entry point to your application henceforth. Another thing that’s happened is it’s created a SecurityContext which stores information of the logged in user. What we must do now is configure it to understand HOW our users will authenticate themselves.

The main class at work here is the Authentication object – it is useful to view things from the POV of this object.

When a existing user makes contact with our system, he comes in with some identifier of himself – this maybe a JWT token or his credentials or even an OTP. One must simply validate that the user comes with at least one of these and encapsulate this information into an Authentication object. The Authentication object itself is managed by the AuthenticationManager.

The AuthenticationManager must now decide HOW to authenticate. If we have n separate modes of authentication, we could be accessing data from n different sources to check if this user is valid! What we do here is create n AuthenticationProvider implementations each dealing with one mode.

Each AuthenticationProvider has two methods :

  1. Authentication authenticate(Authentication authentication);
  2. Boolean supports(Class<?> authClass);

The AuthenticationManager must simply detect all the AuthenticationProvider implementations in the system and loop through them all calling the authenticate(Authentication authentication) method – hoping one gives us a success! In the worst case, we’re running n queries. Unsuccessful authentications becomes especially slow on this system now – you’re spending more bandwidth rejecting users then accepting them!

The Boolean supports(Class<?> authClass) offers a flimsy way to bypass this as one can always call this before executing the authenticate(Authentication authentication) method to validate if the current provider class accepts the data offered by the Authentication object. However, a malicious user could send all the required data – say he sends a token AND a username/password AND an OTP. Now all my authentication providers will respond saying they support this authentication and I end up running n queries. A better way to do this is to define a hierarchy of authentication modes – for eg. if the user comes with both a token and username/password, I will give preference to the token and if the token doesn’t work – the user is rejected! We can only do this as we are not expecting to have users who have an incorrect authentication token but have the correct username/password. If this is not true for your application, then looping through all the providers is the only way to go.

We will do this by tweaking our process of creating the Authentication object itself. Remember how Spring security sets up multiple filters that are accessed before the API is even hit? We can even create our own custom filters that sit ahead of Spring Security filters. That is what we do here, we set a hierarchy of authentication modes – i.e. check for username/password only if no token is present (and so on..). This way our rejection is a single query.

Once our AuthenticationManager has picked an AuthenticationProvider to use, it calls the authenticate(Authentication authentication) method. This method return null if the authentication is a failure and returns the Authentication object with the user’s details if it is a success.

How it does this can differ amongst applications – one may choose to check the encrypted password against a datastore or if it’s an OTP validation – one could store the OTP’s in a quick cache store. The benefit JWT offers us is that it encapsulates user data within it. I need not fetch it from anywhere. I simply need to check if this JWT was signed by us and is unexpired! No database calls!

This data is fetched using an implementation of UserDetails and UserDetailsService which simply serves as a DAO for Spring Security.

Once a user is authenticated, Spring saves this userDetails object in the SecurityContext of the application – in this way it is easily accessible within the code and one can make user level decisions in one’s business logic that is always almost required!

This is a brief on how I used Spring Security effectively and adapted it to fit my use-case. Here is a GitHub repo documenting in hard code what I’ve qualitatively described here :

https://github.com/njari/spring-security-demo

*This may be unavoidable in case I’m doing a direct match eg. – when I’m authenticating with a password. However, there are modes where this is easy to bypass – eg JWT authentication. I simply need to check if the JWT provided is signed by me and is unexpired.

Tree Evolution II

In our array, we always started our searches with the middle element – this enabled us to half our search space each time. Our new data structure should begin at this central point to be optimal and then give us a pointer (as lining them up isn’t feasible when it comes to insertion/deletions) as to where we should go next. This gives us a basic binary tree – the root node is the middle element and contains pointers to two other nodes that lead to elements on the left and right.

Searching through this is identical to how we’d go through a sorted array and takes O(logn) time. The benefit here is that on insertion/deletion, we needn’t move the rest of the elements, we simply need to manipulate the pointers. This makes the actual act of inserting/deleting – O(1). However it takes us O(logn) time to know where to insert/delete. Overall insertion/deletion is O(logn).

The binary search tree seems to be the magical solution here. It is well organised – it is logical and one has a clear path to get to any item one may want. However ,this path may be longer than one expects! log(n) is the best possible tree height, at worst, this could be n! If your path needs you to rummage through every item in succession, it really is no good.

We may, after all, need to spend some organisational effort even while inserting elements to ensure we don’t end up with any particularly bad paths. This leads us to self balancing trees. On each insertion, the current organisation is evaluated and changed if necessary. Where different self balancing tree implementations differ is the conditions under which they deem these organisational changes necessary.

Tree Evolution I

Sure, there’s a place for complex machine learning algorithms in the world and there’s ample space for almost everything else. However, there exists a class of algorithms that occupy more space than any other on a day to day basis.

This is search – simply going through a large dataset and coming with the one(s) you queried for. Basic, menial database searching. If one has a large enough dataset, querying it is going to be increasingly difficult as your dataset increases.

Let’s assume we’re starting with a very naively built dataset which is unsorted and may or may not have duplicates. The only thing one can really do here is to iterate through all of it to find the entry(s) for our query. There really isn’t much else. How do we make this faster? We can do it in a multithreaded manner and divide the dataset in equals parts and iterate through those simultaneously. Heck, we could have multiple systems sifting through disjoint parts of the dataset and then combining their results. This helps with reducing the time taken but has a steep hardware cost associated with it.

The key to making search faster lies in organisation. An easy parallel is one’s room – if you don’t know where your things are kept, you’ll have trouble finding your things. You must be be kind to yourself and keep your things in a sensible place that you know you’ll come back to. Similarly, the key to making queries faster is to keep your items in logical areas so you may go looking in a structured manner.

You could simply number everything and keep it in a single sorted array. You’ll be able to find things pretty fast now. You can simply go to the item right in the middle and compare your query to it. Since this array is sorted, you’ll know to look either to the left or the right and so on. You’ll find your item O(log(n)) time. However, what if there is a new item that must be inserted somewhere in the middle or god forbid, at the start of the array! We’ll have to move each element to the next position to make space for this new one. The same challenge exists for deletion. Maybe we shouldn’t keep everything in one list. Let’s explore a different structure.