简体   繁体   中英

OAuth2 flow for Gateway + Backend Service

I'm currently building a web-application using Spring-Boot-Web in combinition with OAuth2 using Amazon-Cognito on AWS.

My target setup looks like this: 在此处输入图像描述

In "My-Datacenter" I have 2 applications running:

  • Gateway-Application
  • Backend-Application

The Gateway-Application is accessible from the internet. It serves the frontend (html, css, js), contains some display logic and maybe some APIs that are accessible for everyone. It is the initiator for the "Authorization Code Grant"-OAuth 2.0 Flow (it redirects the user to Amazon-Cognito if the user isn't yet logged in).

The Backend-Application is only accessible by the Gateway-Application. It is not accessible from the outside. In the Backend-Application I want to retrieve user-details (Name, E-Mail) from Amazon-Cognito.

What I got to work at the moment:

I registered one client for the Gateway-Application in Amazon-Cognito. The Gateway-Application initiates the "Authorization Code Grant"-Flow and can access the user-information from Amazon-Cognito.

I configured the Gateway-Application to pass the OAuth2-Authorization-Details to the Backend-Application with all my HTTP-Requests. The Backend-Application can successfully check whether the user is authenticated or not.

Gateway-Application configuration

application.yml

spring:
  security:
    oauth2:
      client:
        registration:
          cognito:
            client-name: frontend-local
            client-id: #######
            client-secret: #########
            scope: openid,backend/read,backend/write
            redirect-uri: http://localhost:8080/login/oauth2/code/cognito
        provider:
          cognito:
            issuer-uri: https://cognito-idp.eu-central-1.amazonaws.com/<my-aws-pool-id>
            user-name-attribute: cognito:username

SecurityConfiguration.java

package io.share.frontend.configuration;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .csrf()
                .and()
                .authorizeRequests().antMatchers("/", "/webjars/**").permitAll().anyRequest().authenticated()
                .and()
                .oauth2Login()
                .and()
                .logout().logoutSuccessUrl("/");
    }
}

OAuth2WebClientConfiguration.java

package io.share.frontend.configuration;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProvider;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProviderBuilder;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizedClientManager;
import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
import org.springframework.security.oauth2.client.web.reactive.function.client.ServletOAuth2AuthorizedClientExchangeFilterFunction;
import org.springframework.web.reactive.function.client.WebClient;

@Configuration
public class OAuth2WebClientConfiguration {

    @Bean
    public OAuth2AuthorizedClientManager authorizedClientManager(ClientRegistrationRepository clientRegistrationRepository, OAuth2AuthorizedClientRepository authorizedClientRepository) {

        OAuth2AuthorizedClientProvider authorizedClientProvider =
                OAuth2AuthorizedClientProviderBuilder.builder()
                        .clientCredentials()
                        .build();

        DefaultOAuth2AuthorizedClientManager authorizedClientManager =
                new DefaultOAuth2AuthorizedClientManager(
                        clientRegistrationRepository, authorizedClientRepository);
        authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);

        return authorizedClientManager;
    }

    @Bean
    WebClient webClient(OAuth2AuthorizedClientManager authorizedClientManager) {
        ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2Client =
                new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
        oauth2Client.setDefaultClientRegistrationId("cognito");
        return WebClient.builder()
                .apply(oauth2Client.oauth2Configuration())
                .build();
    }
}

Backend-Application configuration

application.yml

server:
  port: 8081

spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: https://cognito-idp.eu-central-1.amazonaws.com/<my-aws-pool-id>

SecurityConfiguration.java

package io.share.backend.configuration;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests().anyRequest().hasAuthority("SCOPE_backend/read")
                .and()
                .oauth2ResourceServer().jwt();
    }
}

What I want to get to work

In my Backend-Application , I want to be able to access to User-Information from Amazon-Cognito. Currently, I have the following basic RestController in my Backend-Application:

package io.share.backend.web;

import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.security.Principal;

@RestController
public class ObjectResource {

    @GetMapping("/username")
    public String greeting(@AuthenticationPrincipal Principal user) {
        return user.getName();// this currently returns some UUID-Like string, I want to be able to access the username and E-Mail here, just like I can in the Gateway
    }
}

But I want to be able to access the real Username and E-Mail in the Backend.

My question

Do I have to create a new client in Amazon-Cognito for the backend? Or do I have to configure the same client-id and client-secret for the Gateway and Backend?

Which additional Spring-Configurations do I have to make in my Applications (both Gateway and Backend) to make this work?

I'm using spring-boot 2.3.6.RELEASE

Good question and you are running into the following common OAuth issue with APIs:

  • Cognito access tokens contain very little user info, which is good practice - they are not customisable and only include a subject claim
  • Spring wants to create an AuthenticatedPrincipal from just the JWT - but your API also wants additional claims to be added to the AuthenticatedPrincipal

DESIGN PATTERN

There is a design pattern you can use, or borrow ideas from, and my blog post summarises the behaviour. The idea is to define a Claims Principal first, then to populate it with claims from multiple sources at runtime. In your case one source will be the Cognito User Info endpoint.

WORKING CODE

You can run my Sample Spring Boot API , which also uses AWS Cognito, and the key class is the Authorizer . In some setups you may be able to get an API Gateway to do some of this work for you:

PROs and CONs

This pattern will give you extensible claims but it will also add some complexity to your API, since you need to override the API technology stack's default OAuth behaviour.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM