Spring Cloud: Eureka, Zuul and OAuth2 – scaling out authorization server

We are going to touch here a very practical problem, scaling out the Spring OAuth2 authorization server and describing a bit more in detail how this can be done using the Spring Cloud itself. Your authorization server might be requested multiple times during single request processing so you would rather want it to be able to match the demand.

When I first approach the issue I didn’t fully understood which of functionalities Spring Cloud provides and made some false assumptions, so it might be worth describing how to do a basic setup, what is happening under the hood and what you might expect.

In Spring OAuth2 2.0.x you have three very helpful annotations that can enable different functionaries within your application:

  • @EnableOAuth2Resource
  • @EnableOAuth2Sso
  • @EnableOAuth2Client

I want to cover the first two in greater detail.

For each of the annotation there is a corresponding properties that must be to specified. The minimal resource server configuration needed to verify the token and retrieve the user information is to specify the userInfoUri property.

spring:
  oauth2:
    resource:
      userInfoUri: http://localhost:9999/uaa/user

This will allow for the incoming requests to be verified using Barer Token authentication.

So most likely you are going to enable resource server on probably every microservice that you are going to develop. On one of your edge services you may want to enable the single sign on. The @EnableOAuthSso will allow you to turn automatic redirection of your unauthicated user towards the authorization server, where they will be able to log in.

Similar you will have to specify some extra properties like the address of the authorization server token and authorization uri together with client credentials. We can also specify which paths within our application should be secured.

spring:
  oauth2:
    sso:
      home:
        secure: true
        path: /,/**/*.html
    client:
      accessTokenUri: http://localhost:9999/uaa/oauth/token
      userAuthorizationUri: http://localhost:9999/uaa/oauth/authorize
      clientId: edge
      clientSecret: secret

Now this is a minimal setup that is going work on a single node (of course you can put the authorization server behind any load balancer).

This is not all, if you import spring-cloud-security module (in version at least 1.0.2) you can expect fallowing. On each resource server the auto configuration class is going to setup OAuth2RestTemplate that will propagate the authentication token and if you happen to have discovery client enabled (by importing Eureka client or Consul and setting it up correctly) it will be also be capable of discovering and distributing the load towards registered services.

Also you can achieve the same for the Feign clients through custom extension.

Finally if you set spring.oauth2.resource.loadBalanced to true, you will configure client load balancing for retrieval of the user information from authorization servers (when you run up multiple nodes and register them in the discovery service):

spring:
  oauth2:
    resource:
      loadBalanced: true
      userInfoUri: http://oauth2/uaa/user

This feature isn’t specially documented, and I happen to found it by going through the Spring Cloud Security source code.

This is already neat, but what about our single sign-on? At the moment we had to specify a single address.

In the project that I had been working on we have addressed the issue in two different ways in two bit different configurations. In one of the applications that was a single-page application we completely resigned from the build in server side redirection and used instead AngularJS and simple AJAX POST for authentication. For that to work we had to either enable CORS (which the authorization server dosen’t do, but you can find a interesting article how to do this) or configure a reverse proxy – like Zuul. We had chosen the latter. Through simple setup you can route the authorization AJAX request through Zuul towards the authorization server:

zuul:
  ignoredServices: '*'
  routes:
    oauth2:
      path: /uaa/**
      serviceId: oauth2
      stripPrefix: false

Finally if you want to use Spring’s single sign-on you can do that, by setting up the Zuul proxy as your edge gateway and correctly configure the route to the auto discovered nodes replacing accessTokenUri and userAuthorizationUri with the edge gateway address (preferable the DNS name). Of course there wouldn’t be any reason not to use any other load balancer instead, especially if your cloud provider offers one out of the box. Although the build in Zuul proxy auto discovery is very tempting to use here.

So to sum up, for your “internal” communication the resource servers can use the build in Spring Cloud load balancing support with help of the discovery service. When the “external” world needs to communicate with your OAuth2 server, you should put load balancer in front of it, with Netflix Zuul being natural candidate making also the solution portable across different cloud providers as well as containers, like Docker for instance.

With such setup you are now able to add as many authorization server nodes as you want to.

9 comments

  1. kakawait · January 14, 2016

    Pretty smart! I didn’t know about `spring.oauth2.resource..loadBalanced`. I have same scenario, UAA behind `Zuul` but until now I didn’t find a way to not hardcode `accessTokenUri` and `userAuthorizationUri` with `Zuul` hostname.

    But now with `loadBalanced` I can use `Eureka` mapping!

    Like

  2. Sridhar · March 15, 2016

    Hi,

    I found your article interesting.

    I have a query, not sure if you can help me out on this.

    I have many atomic services which are being invoked from a composite service.

    I have secured the atomic services using a AUTH service which is basically a OAuth2 provider and using @EnableResourceserver annotation at each of the atomic services.

    Also, I have secured the methods in the services with method level security for Role based checks and ACL checks for object level access using Spring Security annotations.

    Now, I am working on fronting them all with a zuul proxy which is supposed to hide the actual url and forward the request to respective composite services based on the rules configured.

    My question is should I also apply the security at zuul proxy level making it also a Resource Service using Spring Security OAuth2 and again repeat the role checks for the url mappings there or just forward the request to the target service which is anyway going to delegate to the OAuth2 provider for token validation.

    I feel just forwarding to the target service would make work easier.

    Would moving all the Role based checks and ACL checks which I have at each of the Atomic micro services to the ZUUL Proxy app make sense ?. I see the advantage of making the atomic business services focus only on the business concern.

    Even if I move all to the zuul proxy, I am not sure if there a way to prevent a composite or atomic service to be directly accessed from both internally and externally. If not, I will end up duplicating the logic in every layer, including composite and all atomic services.

    Can you let me know your views on would it be better to localize it within every atomic service or move it all to the ZUUL Proxy.

    Thanks,
    Sridhar

    Like

  3. Avnish · October 14, 2016

    Nice Article.Where can i find the source code

    Like

  4. Timl Schimandle · October 14, 2016

    Could you add your source code for this article?

    Liked by 1 person

    • Jakub Narloch · October 16, 2016

      There is no really much to post, all that is there is the configuration that can be put into the application.yml and that’s it.

      You can really setup everything easy from the starters: https://start.spring.io

      Like

  5. Gadi Eichhorn · November 19, 2016

    nice post!

    have you tried this with the latest 1.4.1 release Camden.SR2?
    I am not sure this loadBalanced: true is available.

    I have OAuth2 & UI & Resource behind Zuul and I try to wire it all up, can you share some tips how this can be done? It seemed to fail on the redirects between the UI and the UAA .

    There is a lot of art in getting this to work so a small working example will be great 🙂

    Like

  6. XXXXX · December 1, 2016

    where could i pull the source code?

    Like

  7. Paul Pelludat · January 3, 2017

    do you now a possibility to make this possible with Keycloak?
    I tried everything.
    But @EnableOAuth2Sso in Spring Zuul and endpoints like you mentioned doesn’t work

    Like

  8. Mouli S · April 6, 2017

    Hi,
    Can someone give the exact configuration for these properties ?
    security.oauth2.client.accessTokenUri=http://localhost:8888/uaa/oauth/token
    security.oauth2.client.userAuthorizationUri=http://localhost:8888/uaa/oauth/authorize

    I want to enable Eureka lookup for this using Zuul but its not working. Here are the configurations I tried.
    zuul.routes.oauth2.path = /uaa/**
    zuul.routes.oauth2.serviceId = authserver
    zuul.routes.oauth2.stripPrefix = false
    security.oauth2.resource.loadBalanced = true

    Please any help is appreciated.

    Like

Leave a comment