简体   繁体   English

网关 + 后端服务的 OAuth2 流

[英]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.我目前正在使用 Spring-Boot-Web 结合 OAuth2 在 AWS 上使用 Amazon-Cognito 构建一个 Web 应用程序。

My target setup looks like this:我的目标设置如下所示: 在此处输入图像描述

In "My-Datacenter" I have 2 applications running:在“我的数据中心”中,我运行了 2 个应用程序:

  • Gateway-Application网关应用
  • Backend-Application后端应用

The Gateway-Application is accessible from the internet.网关应用程序可从 Internet 访问。 It serves the frontend (html, css, js), contains some display logic and maybe some APIs that are accessible for everyone.它服务于前端(html、css、js),包含一些显示逻辑,也许还有一些每个人都可以访问的 API。 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).它是“授权代码授予”-OAuth 2.0 流程的发起者(如果用户尚未登录,它会将用户重定向到 Amazon-Cognito)。

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.在后端应用程序中,我想从 Amazon-Cognito 检索用户详细信息(姓名、电子邮件)。

What I got to work at the moment:我现在要做的事情:

I registered one client for the Gateway-Application in Amazon-Cognito.我在 Amazon-Cognito 中为 Gateway-Application 注册了一个客户端。 The Gateway-Application initiates the "Authorization Code Grant"-Flow and can access the user-information from Amazon-Cognito.网关应用程序启动“授权代码授予”流程,并可以从 Amazon-Cognito 访问用户信息。

I configured the Gateway-Application to pass the OAuth2-Authorization-Details to the Backend-Application with all my HTTP-Requests.我将网关应用程序配置为将 OAuth2-Authorization-Details 与我的所有 HTTP 请求一起传递给后端应用程序。 The Backend-Application can successfully check whether the user is authenticated or not. Backend-Application 可以成功检查用户是否通过身份验证。

Gateway-Application configuration网关-应用程序配置

application.yml应用程序.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安全配置.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 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应用程序.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安全配置.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.在我的Backend-Application中,我希望能够从 Amazon-Cognito 访问用户信息。 Currently, I have the following basic RestController in my Backend-Application:目前,我的后端应用程序中有以下基本 RestController:

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?我是否必须在 Amazon-Cognito 中为后端创建一个新客户端? Or do I have to configure the same client-id and client-secret for the Gateway and Backend?还是我必须为网关和后端配置相同的客户端 ID 和客户端密码?

Which additional Spring-Configurations do I have to make in my Applications (both Gateway and Backend) to make this work?我必须在我的应用程序(网关和后端)中进行哪些额外的 Spring-Configurations 才能使其工作?

I'm using spring-boot 2.3.6.RELEASE我正在使用 spring-boot 2.3.6.RELEASE

Good question and you are running into the following common OAuth issue with APIs:好问题,您在 API 中遇到了以下常见的 OAuth 问题:

  • Cognito access tokens contain very little user info, which is good practice - they are not customisable and only include a subject claim Cognito 访问令牌包含非常少的用户信息,这是一种很好的做法 - 它们不可自定义,仅包含主题声明
  • Spring wants to create an AuthenticatedPrincipal from just the JWT - but your API also wants additional claims to be added to the AuthenticatedPrincipal Spring 想要仅从 JWT 创建一个 AuthenticatedPrincipal - 但是您的 API 还希望将其他声明添加到 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.在您的情况下,一个来源将是 Cognito 用户信息端点。

WORKING CODE工作代码

You can run my Sample Spring Boot API , which also uses AWS Cognito, and the key class is the Authorizer .您可以运行我的Sample Spring Boot API ,它也使用 AWS Cognito,密钥 class 是Authorizer In some setups you may be able to get an API Gateway to do some of this work for you:在某些设置中,您可能可以获得 API 网关来为您完成其中的一些工作:

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.此模式将为您提供可扩展的声明,但也会为您的 API 增加一些复杂性,因为您需要覆盖 API 技术堆栈的默认 OAuth 行为。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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