Lessons learnt: Of Spring Boot + OAuth2 + redirect URIs

TL;DR: make sure NGINX is setup correctly (proxy_set_header) before messing around with your code.

Scenario: Deploying a Spring Boot micro-service behind an NGINX reverse proxy gave us issues when using default Google OAuth2 configuration as described here , basically showing the “Redirect URI Mismatch” mentioned at the very end of the linked article

Trying the solution based on security.oauth2.client.pre-established-redirect-uri as mentioned in this article didn’t make any difference, looking at the debug spring security logs showed the framework was still redirecting requests to the default redirect URL

First Attempt (an interesting detour but ultimately failed attempt)

What did make a difference was the following application property we found from the spring boot documentation:

spring.security.oauth2.client.registration.google.redirect-uri-template: http://abc.example.com/login/oauth2/callback/google

However, it is important to set the redirectionEndpoint baseURI as part of the configuration, for example:

@Configuration
class OAuth2LoginSecurityConfig : WebSecurityConfigurerAdapter() {
    @Throws(Exception::class)
    override fun configure(http: HttpSecurity) {
        http
                .authorizeRequests()
                    .antMatchers("/login.html", "/favicon.ico")
                        .permitAll()
                    .anyRequest().authenticated()
                    .and()
                        .oauth2Login()
                            .redirectionEndpoint().baseUri("/login/callback/code/*")
    }
}

Note how the baseUri is set to only the path portion of the redirect-uri-template, and does not include the hostname.

Second Attempt (correct solution)

The above almost got us to the correct solution, however Spring Boot threw up the error of “invalid redirect uri“. Looking at the code, it transpires that Spring Oauth2 checks the redirect URI returned to it from the authentication provider (Google in our case) with a redirect URI it builds on the fly – using the hostname it detects from the original incoming HTTP Request (i.e. the request coming from the front-end).

Since this HTTP Request passes through an NGINX reverse proxy, Spring Boot was actually seeing “localhost” in the HTTP Host Header, and since localhost does not match the redirect-uri-template we set above (abc.example.com in our particular example), then Spring Boot throws up the invalid redirect uri error.

So it turns out the solution was really rather simple:

  1. Remove the changes introduced in the first attempt (i.e. the custom redirectionEndpoint settings and the redirect-uri-template)
  2. Instruct NGINX to copy the HTTP host header it received from the original client, into the HTTP hos header that it sends to the server, thereby preserving the HTTP Host header, like so:
proxy_set_header Host $http_host;

This is equivalent to Apache’s ProxyPreserveHost setting

Advertisements

Lessons learned: Spring Data Postgres application configuration

Scenario: During development of a Spring Boot application, with a PostgreSQL backend, we randomly observe errors such as:

hikaripool-1 - connection is not available, request timed out after 30000ms

 

FATAL: remaining connection slots are reserved for non-replication superuser connections

 

Solution:

There are a few checks to perform:

  1. Ensure that you use the correct Spring annotations to support the JpaRepository, such as the @Transactional and @Modifying annotations.  These help in making sure connections are closed and flushed
  2. Pay special attention to places in your code where long running or computationally heavy database queries are made. You can check which queries are currently active on your PostgreSQL database by logging into the psql command line and running the command: “select * from pg_stat_activity“. Ideally, most of the connection slots displayed should be in the “idle” state.
  3. The definitive solution in our case was to modify the connection pool properties in the application.properties file of the Spring Boot application, as follows:
spring.datasource.hikari.minimumIdle=5
spring.datasource.hikari.maximumPoolSize=20
spring.datasource.hikari.idleTimeout=30000
spring.datasource.hikari.poolName=SpringBootJPAHikariCP
spring.datasource.hikari.maxLifetime=2000000
spring.datasource.hikari.connectionTimeout=30000