• Stars
    star
    322
  • Rank 130,398 (Top 3 %)
  • Language
    Shell
  • Created over 8 years ago
  • Updated over 6 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Spring AuthorizationServer load balanced behind Zuul

UAA (AuthorizationServer) load balanced behind API-GATEWAY (Edge service Zuul)

Twitter Follow

Disclamer

This project is Proof of concept (aka PoC) (and code quality is not perfect), please before using in production review security concerns among other things. (See #6)

Change Log

see CHANGELOG.md

Overview

Quick&dirty sample to expose how to configure AuthorizationServer (UAA) behind Zuul

This way to do may not work for all kind of configuration (I do not test without JWT and prefer-token-info: true)

Usage

Please deploy every services using docker way or maven way, then simply load http://localhost:8765/dummy on your favorite browser.

Default user/password is user/password

Docker

Start building docker images for every services, simply run following command on root directory

mvn clean package -Pdocker

Launch services using docker-compose

docker-compose up -d

Maven

On each service folder run following command:

mvn spring-boot:run

Run

Open http://localhost:8765/dummy and connect with:

  • user: user
  • password: password

Goals

  1. Avoid any absolute/hardcoded urls for security.oauth2.client.accessTokenUri & security.oauth2.client.userAuthorizationUri in order to improve portability!
  2. AuthorizationServer distribution for HA
  3. Do not expose AuthorizationServer, like other service AuthorizationServer will be behind Zuul

network

Where localhost:8765 is Zuul, as you can see AuthorizationServer is not leaked outside! Only Zuul is targeted.

ATTENTION for 2. you should manage yourself shared storage backend (unlike following sample)! Using database or something els.

Keys points of the sample

[Zuul] Custom OAuth2ClientContextFilter

I had to override OAuth2ClientContextFilter to support URI and not only URL (see DynamicOauth2ClientContextFilter). Indeed URL does not support path like /uaa/oauth/authorize.

Why adding path support on OAuth2ClientContextFilter?

Because I want to use path on security.oauth2.client.userAuthorizationUri in order to redirect user to Zuul itself.

On this case I can't use http://localhost:${server.port}/uaa/oauth/authorize because security.oauth2.client.userAuthorizationUri is use on web redirection (header Location: ).

Flow will look like following

Browser                             Zuul                               UAA
   │        /dummy                   │                                  │
   ├────────────────────────────────>│                                  │
   │  Location:http://ZUUL/login     │                                  │
   │<┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┤                                  │
   │        /login                   │                                  │
   ├────────────────────────────────>│                                  │
   │  Location:/uaa/oauth/authorize  │                                  │
   │<┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┤                                  │
   │     /uaa/oauth/authorize        │                                  │
   ├────────────────────────────────>│                                  │
   │                                 │      /uaa/oauth/authorize        │
   │                                 ├┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄>│
   │                                 │                                  ├──┐
   │                                 │                                  │  │ Not authorize
   │                                 │                                  │<─┘
   │                                 │  Location:http://ZUUL/uaa/login  │
   │                                 │<┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┤
   │                                 │                                  │
   │ Location:http://ZUUL/uaa/login  │                                  │
   │<┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┤                                  │
   │       /uaa/login                │                                  │
   ├────────────────────────────────>│                                  │
   │                                 │            /uaa/login            │
   │                                 ├┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄>│
   │                                 │           LOGIN FORM             │
   │                                 │<┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┤
   │           LOGIN FORM            │                                  │
   │<────────────────────────────────┤                                  │

Take attention on second redirection, location is using path (not at browser level).

[Zuul] localhost trick for security.oauth2.client.accessTokenUri

Unlike security.oauth2.client.userAuthorizationUri, security.oauth2.client.accessTokenUri is not used a browser level for redirection but used by RestTemplate. However default RestTemplate used for accessTokenUri is not load balanced thus we can't use url like http://service-name/oauth/token.

We can simply add load balanced feature by adding such Bean

@Bean
UserInfoRestTemplateCustomizer userInfoRestTemplateCustomizer(SpringClientFactory springClientFactory) {
    return template -> {
        AccessTokenProviderChain accessTokenProviderChain = Stream
                .of(new AuthorizationCodeAccessTokenProvider(), new ImplicitAccessTokenProvider(),
                        new ResourceOwnerPasswordAccessTokenProvider(), new ClientCredentialsAccessTokenProvider())
                .peek(tp -> tp.setRequestFactory(new RibbonClientHttpRequestFactory(springClientFactory)))
                .collect(Collectors.collectingAndThen(Collectors.toList(), AccessTokenProviderChain::new));
        template.setAccessTokenProvider(accessTokenProviderChain);
    };
}

An opened isse exists spring-attic/spring-security-oauth#671

[Zuul] Clear sensitiveHeaders lists for AuthorizationServer route

Since Brixton.RC1, Zuul filters some headers (http://cloud.spring.io/spring-cloud-static/spring-cloud.html#_cookies_and_sensitive_headers).

By default it filters:

  • Cookie
  • Set-Cookie
  • Authorization

But we need that AuthorizationServer could create cookies so we must clear list

zuul:
  routes:
    uaa-service:
      sensitiveHeaders:
      path: /uaa/**
      stripPrefix: false

TODO Check if zuul.routes.uaa-service.sensitiveHeaders: Authorization could work?

[Zuul] Disable XSRF at gateway level for AuthorizationServer

AuthorizationServer has it own XSRF protection so we must disable at Zuul level

private RequestMatcher csrfRequestMatcher() {
    return new RequestMatcher() {
        // Always allow the HTTP GET method
        private final Pattern allowedMethods = Pattern.compile("^(GET|HEAD|OPTIONS|TRACE)$");

        // Disable CSFR protection on the following urls:
        private final AntPathRequestMatcher[] requestMatchers = { new AntPathRequestMatcher("/uaa/**") };

        @Override
        public boolean matches(HttpServletRequest request) {
            if (allowedMethods.matcher(request.getMethod()).matches()) {
                return false;
            }

            for (AntPathRequestMatcher matcher : requestMatchers) {
                if (matcher.matches(request)) {
                    return false;
                }
            }
            return true;
        }
    };
}

[Zuul] Authorize request to AuthorizationServer

Ok should I really need to explain why?

http.authorizeRequests().antMatchers("/uaa/**", "/login").permitAll()

ATTENTION do not use "/uaa/**" authorize only necessary API (I was to lazy)

[UAA] Deploy AuthorizationServer on isolated context-path

Zuul and AuthorizationServer have to manage their own session! So both have to write two JSESSIONID cookies.

You must isolate AuthorizationServer on other context-path server.context-path = /uaa to avoid any cookies collision.

ALTERNATIVE we can check if server.session.cookie.path or server.session.cookie.name is not sufficient, I did not test it.

[UAA] Enable server.use-forward-headers

Does not work without. I will not explain why, please look about X-Forwarded-* headers for more information.