6

We currently have 4 Spring applications that use Spring Security Oauth2 project for authentication. The applications are REST APIs that are consumed by other internal applications in the company I work for.

Everything was working fine in the development and QA environments as we were not doing load balancing, now that we are in pre-production we are facing an issue with the load balancer (LB).

This is the workflow for this issue:

  1. Client sends request for the oauth token
  2. LB redirects the request to Box 1
  3. Box 1 authenticates and returns a valid Bearer Token
  4. Client receives the token and store it for using through the sesion
  5. Client sends request for a service in the REST API adding the previously retrieved token to the headers
  6. LB redirects the request to Box 2
  7. Box 2 fails to authenticate as it does not recognize the token and returns an Invalid Credentials response

We are using an in memory user store:

<bean id="tokenStore" class="org.springframework.security.oauth2.provider.token.InMemoryTokenStore" />

Is there a way to make different boxes to share the same token store? I know there is a JdbcTokenStore that can be used to persist tokens to the db, but I would prefer to avoid persisting tokens as these applications point to a legacy database that only stores business information.

Edwin Dalorzo
  • 76,803
  • 25
  • 144
  • 205
raspacorp
  • 5,037
  • 11
  • 39
  • 51
  • 3
    I'm pretty sure the `InMemoryTokenStore` is not meant for production use. For example, what happens if one server goes down? All the tokens it had in local memory are now lost. You're gonna have to use some kind of shared resource across servers - either database, distributed cache, shared file system, etc. – superEb Sep 17 '13 at 00:42
  • Did you solve this problem? I'm facing the same problem. – polis Jun 11 '20 at 05:31

3 Answers3

8

I'm a bit late to this question, but maybe this will help someone searching for a similar answer. There are two major things you need to watch out for when using multiple oauth servers on a load balancer:

As @chris-h alluded to in his answer, you need to ensure that the information backing an access token issued by any of the oauth servers can be read (and trusted) by any of the other oauth servers. You can use a JDBC token store as he suggested, but this has the disadvantage that if Server A has to validate an access token issued by Server B, it always has to hit the database to do so.

A better solution (IMO) is to use JWT access tokens, where all the information needed to validate the token is encrypted within it. So long as all oauth servers use the same encryption key, they can read the data in each other's access tokens and trust that the data is valid since it is encrypted. The advantage is there are no database calls needed to validate an access token. The disadvantage is that there's no easy way to invalidate an access token once it's been issued. If you ever wondered why refresh tokens are needed when you could just increase the expire time of the access tokens themselves, this is the biggest reason.

The second thing to be aware of is that the Spring oauth implementation uses sessions to keep track of where the user is an the authentication process. If you aren't careful, you could wind up in an "endless loop" scenario. Suppose you have two oauth servers--Server A and Server B:

  1. The user goes to a web page or service which requires authentication, so gets redirected to "foo.com/oauth/authorize". The load balancer sends this request to Server A.
  2. Since Server A doesn't have any session information which proves the user is already authenticated, it redirects the user to the login page at foo.com/oauth/login. The redirect goes back through the load balancer, and since the load balancer is working in a "round-robin" fashion, this time it sends the request to Server B.
  3. The user logs in successfully, so session information is written to keep track of this. This session information is only known to Server B.
  4. Since the login was successful, the user gets redirected back to "foo.com/oauth/authorize" to continue the authentication process. Once again the redirect goes back through the load balancer. Since the load balancer is working in a "round-robin" fashion, this time it sends the request to Server A. But Server A doesn't know about the successful login that happened on Server B. Go back to step 2!

Probably the best (current) solution to this problem is to ensure that your load balancer supports "sticky sessions"--that is, once it sends a particular user to Server A or Server B, it always sends that user to the same server for a while.

A better solution might be for oauth implementations to not use sessions at all. Instead, use encrypted data passed as a parameter to /oauth/* that denotes where you are in the login process. Similar to how JWT tokens work, the information can be trusted by all servers if they all share the encryption key.

Alvin Thompson
  • 5,388
  • 3
  • 26
  • 39
3

All authentication servers and all resource servers must share the same tokenStore in order to validate tokens.

That means switching to the JdbcTokenStore or a custom TokenStore implementation which is capable of sharing tokens between servers in some way (shared datastore, NFS shared filesystem, etc.). Of course, it may even be possible to share an InMemoryTokenStore using Terracotta or a similar memory-sharing product if you are willing to go that route.

Chris H.
  • 2,204
  • 1
  • 19
  • 18
1

Specifically for implementing authorization code grant to work with load balancer I stored Spring sessions in Redis. Redis being a shared resource ensures Spring session information is shared among all the running instances of the Spring application.

There is an alternative approach evaluated by having sticky sessions in place at load balancer which is also a good option but this implementation requires session replication in order to maintain HA.

Sticky Sessions Sticky Sessions

IMHO Centralized cache store implementation gives more control at the application end having all configurations at the application with guaranteed HA without any additional overheads.

With Cache Store With Cache Store

Below is how the sessions are stored in Redis

application.properties

spring.session.store-type=redis
server.servlet.session.timeout=3600s
spring.session.redis.flush-mode=on-save
spring.session.redis.namespace=spring:session

And for overriding HttpSession management

@Configuration
@EnableRedisHttpSession
public class HttpSessionConfig extends AbstractHttpSessionApplicationInitializer {

@Bean
public JedisConnectionFactory redisConnectionFactory() {

    ...
    JedisConnectionFactory jedisConFactory = new JedisConnectionFactory(redisConfig);
    ...
    return jedisConFactory;

    }
}
qwerty
  • 2,392
  • 3
  • 30
  • 55