Spring Cloud Gateway — API Access Control with JWT

er.akashgupta27
5 min readFeb 25, 2021

--

API Gateway pattern is one of the commonly used patterns in Microservice Architecture in which API Gateway acts as a reverse proxy routing request from the client to the server.

In this article, we’ll explore one of the most important aspects - API Security & Access Control - at the Gateway layer. The main purpose is to block unauthorized API request at Gateway itself. We are going to use Spring Cloud Gateway and Spring OAuth2 Resource Server along with Okta as Authorization server. Below is high level flow of how it is going to work -

Spring Cloud Gateway for OAuth2 Resource Server

Spring Cloud Gateway is API Gateway implementation by Spring Cloud team on Spring Boot 2.x, Spring WebFlux, and Project Reactor. It requires Netty server to provide non-blocking asynchronous request processing.

What are we going to use

Setting-up Sample Microservice

For routing from Gateway, we are going to create a simple Welcome REST service with two different endpoints (for simplicity, one is considered as unsecured and another one is secured). Following is the sample source code for WelcomeController -

Setting-up Spring Cloud Gateway

This is where we configure the routing and apply security for our backend services. First, let us create a new spring boot project with spring-cloud-gateway and oauth2-resource-server dependencies. Below is the sample pom.xml -

Adding Security with OAuth2 Resource Server and JWT

We are now going to add oauth2 resource server configuration to our Gateway. Create a new ResourceServerSecurityConfig.java to add security for Gateway routes as follows. It will configure the default OAuth2 Resource Server Support with JWT.

To use JWT, we need to provide public key details of Authorization server which can be used to decode incoming access token. This can be set-up in one of the three ways -

  1. If public key certificate is available, it can be included as part of the project in resources folder and then property spring.security.oauth2.resourceserver.jwt.public-key-location can be set to same location.
  2. If Authorization server exposes a JWKS URI, it can be used with property spring.security.oauth2.resourceserver.jwt.jwk-set-uri. Since we are using Okta and it exposes the endpoint for the same, we are going to use this in our application property.
  3. Lastly, if Authorization server supports OpenID Connect, we can set OpenID Connect discovery endpoint or an OAuth 2.0 Authorization Server Metadata endpoint with property spring.security.oauth2.resourceserver.jwt.issuer-uri.

Once this is set-up, any route accessed through gateway will have to pass an access token with Bearer Authorization header. If Authorization header is not present, Gateway will through 401 without any error response. We’ll see later how to customize it.

Configure Unsecured Routes

At times, we don’t want to secure all the endpoints and leave some of them open and unsecured.

Unsecured (or unprotected) routes are the routes for which no additional security is applicable. They can be accessed by anyone without any Authorization header. Typically, these are routes which doesn’t provide any sensitive information and can be exposed without any security risks.

We can achieve this by two ways -

  1. Modifying ResourceServerSecurityConfig.java with additional filtering of URL patterns and then permitAll() these patterns.
  2. Create a new Configurable PublicSecurityConfig which can be used to configure URLs at run time and if required, it can be disabled completely as well -

The unsecured routes configuration can be disabled by setting custom property custom.gateway.expose-unsecured-urls to false. The URL patterns which need to be unprotected can be configured using custom property custom.gateway.unsecured-urls.

Putting it together with Application properties

Now let us update the route for Welcome service and make one of the route as unsecured for demo purpose -

Try It Now

GitHub Project Link — https://github.com/akashgupta2703/cloud-gateway-resource-server

  1. Run Welcome Service at Port 8080 and Run Gateway at Port 9000.
  2. Access the unsecured endpoint (http://localhost:9000/api/v1/welcome/unsecured-hello) without any Authorization header :
Unsecured Endpoint Access

3. To access secured endpoint, Generate the access token using Okta client credentials and pass the generated access token as Bearer Token to secured API (http://localhost:9000/api/v1/welcome/secured-hello). Sample request and response are :

Secured Endpoint Access

Advanced Customization

  • Bearer Token Resolver

By Default, OAuth2 Resource Server resolves the access token from Authorization header. This can be further enhanced to accept access token from ‘access_token’ query parameter as follows -

private ServerAuthenticationConverter serverAuthenticationConverter() {
ServerBearerTokenAuthenticationConverter serverAuthenticationConverter = new ServerBearerTokenAuthenticationConverter();
serverAuthenticationConverter.setAllowUriQueryParameter(true);
return serverAuthenticationConverter;
}

Then refer the same in the configuration -

oauth2ResourceServer().bearerTokenConverter(serverAuthenticationConverter())

This can be again fully customized to accept access token from any place by either implementing ServerAuthenticationConverter or extending the default authentication converter ServerBearerTokenAuthenticationConverter class.

  • Customize Error Response

By default, OAuth2 Resource server sends 401 along with error details in WWW-Authenticate response header. This may not be very user friendly. We can customize error response either by implementing ServerAuthenticationEntryPoint or extending the default authentication entry pointBearerTokenServerAuthenticationEntryPoint class. This can be attached to security chain similar to Bearer Token Resolver i.e.

oauth2ResourceServer().authenticationEntryPoint(customBearerTokenServerAuthenticationEntryPoint())
  • Custom Access Denied Handler

Similar ways, Access Denied can also be customized by either implementing ServerAccessDeniedHandler or extending the default BearerTokenServerAccessDeniedHandler class.

  • Custom Token Validations

By default, OAuth2 resource server only validates the expiry of JWT. We can create our own token validators implementing OAuth2TokenValidator interface e.g. we can create validator to validate role, authorities, scopes, issuer etc.

We need to then expose NimbusReactiveJwtDecoder as bean and set the custom validators.

@Bean
public ReactiveJwtDecoder reactiveJwtDecoder() {
NimbusReactiveJwtDecoder reactiveJwtDecoder = new NimbusReactiveJwtDecoder(jwkSetUrl);
reactiveJwtDecoder.setJwtValidator(customOAuth2TokenValidators());
return reactiveJwtDecoder;
}

Check JwtTimestampValidator class for sample implementation.

References

Spring Cloud Gateway Official Documentation — https://cloud.spring.io/spring-cloud-gateway/reference/html/

Okta Authorization Server — https://developer.okta.com/docs/guides/customize-authz-server/create-authz-server/

--

--

er.akashgupta27

Avid Programmer, Keen Experimenter. Works mainly with Java and Spring Ecosystem.