如何在不使用 API 网关的情况下使用 AWS Cognito 进行自定义 Spring REST API 授权

How to Use AWS Cognito without using API Gateway for Custom Spring REST API Authorization

场景:
我将在任何 EC2 中 Spring 引导 REST API 应用程序 运行 并公开多个 API,我没有为它们使用 AWS API 网关。
我需要多个用户访问具有不同角色(Admin、WriteUser、ReadUser)的多个 API。
我可以在 Cognito 用户池中创建用户和组,并且可以在 IAM 中拥有多个角色。

如何配置 AWS Cognito 的 UserPool 和 IdentityPool 以根据用户角色授权不同的 APIs 访问?有什么方法可以在不使用 API 网关的情况下在策略(与 IdentityPool/Resource 服务器关联)中定义这样的 "API allow rules based on role"。

提前致谢。

您需要使用 Spring 安全性实施 JWT 令牌方法(可以使用 spring 安全身份验证 2 实现。)

所以授权步骤如下:

  • 用户将在您的 front-end 应用中输入 his/her 凭据。
  • 您的身份验证服务会将这些凭据发送到 AWS Cognito 进行验证。
  • 如果用户凭证将在 AWS Cognito 用户响应的帮助下进行验证,您将创建一个负载,然后创建一个 JWT 令牌。
  • 使用 public-private 密钥签署 JWT 令牌,以便您可以与其他服务共享您的 public 密钥以验证 JWT 令牌。
  • 然后 JWT 令牌 return 到 front-end app.
  • 现在 Spring 安全部门将为您保护所有私人路线。
  • 您还需要创建一个过滤器,这样当 front-end 应用程序通过在 header 中附加 JWT 创建对服务器的 ant 请求时,您的过滤器将验证 JWT 令牌(作为身份验证 header) 如果令牌有效则将为当前请求创建安全上下文否则抛出 401 状态。

我使用带有自定义 JWTFilter 的 Spring 安全 auth2 实现,它将从请求中获取令牌并再次验证认知池的 JWKs 验证文件。

  • 用户在 UI 中输入 username/password。
  • UI 调用后端应用程序,后者调用 Cognito。
  • Cognito returns JWT 令牌(Idtoken、accessToken、refreshTokens)
  • 后端应用程序将 IdToken 作为令牌发送到 UI 并存储在缓存中(也可以是任何数据库)。
  • UI 发送此 IdToken 以供下次调用。

JWTFilter 执行以下步骤进行身份验证:

  • 后端应用程序获取令牌,如果存在则在缓存中进行验证,然后使用 Cognito JWK 验证签名和到期时间并从中获取详细信息。
  • 后端应用程序创建扩展 AbstractAuthenticationToken 的 UserAuthentication 并根据解析的令牌值填充详细信息并存储在上下文(会话)中

资源授权将按照网络安全配置中提到的配置完成。

我们可以创建 Spring 启动资源服务器,将 Cognito 作为身份提供者。

依赖关系:

    <!--  Spring Security-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.security.oauth.boot</groupId>
        <artifactId>spring-security-oauth2-autoconfigure</artifactId>
        <version>2.0.1.RELEASE</version>
    </dependency>

Spring 安全配置:

EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class OAuth2ResourceServerSecurityConfiguration extends ResourceServerConfigurerAdapter {

  private final ResourceServerProperties resource;

  public OAuth2ResourceServerSecurityConfiguration(ResourceServerProperties resource) {
    this.resource = resource;
  }

  @Override
  public void configure(HttpSecurity http) throws Exception {

    http.cors();

    http.csrf().disable();

    http.authorizeRequests()
        .antMatchers("/api/public/**").permitAll()
        .antMatchers("/actuator/health").permitAll()
        .anyRequest().authenticated();
  }


  // Note: Cognito Converter
  @Bean
  public TokenStore jwkTokenStore() {
    return new JwkTokenStore(
        Collections.singletonList(resource.getJwk().getKeySetUri()),
        new CognitoAccessTokenConverter(),
        null);
  }
}

Cognito 访问令牌转换器:

我们在这里将 Cognito 声明转换为 Spring 安全消耗格式。

对于授权,我们将使用 Cognito 组。我们创建两个组,ROLE_ADMIN & ROLE_EMPLOYEE。我们将用户映射到每个组。当用户通过身份验证时,我们将 Cognito 组作为声明。我们利用它为用户设置 Spring 安全权限。

@Component
public class CognitoAccessTokenConverter extends JwtAccessTokenConverter {

  // Note: This the core part.
  private static final String COGNITO_GROUPS = "cognito:groups";
  private static final String SPRING_AUTHORITIES = "authorities";
  private static final String COGNITO_USERNAME = "username";
  private static final String SPRING_USER_NAME = "user_name";

  @SuppressWarnings("unchecked")
  @Override
  public OAuth2Authentication extractAuthentication(Map<String, ?> claims) {

    if (claims.containsKey(COGNITO_GROUPS))
      ((Map<String, Object>) claims).put(SPRING_AUTHORITIES, claims.get(COGNITO_GROUPS));
    if (claims.containsKey(COGNITO_USERNAME))
      ((Map<String, Object>) claims).put(SPRING_USER_NAME, claims.get(COGNITO_USERNAME));
    return super.extractAuthentication(claims);
  }
}

application.properties

server:
  port: 8081
security:
  oauth2:
    resource:
      userInfoUri: https://<cognito>.auth.eu-west-1.amazoncognito.com/oauth2/userInfo
      tokenInfoUri: https://<cognito>.auth.eu-west-1.amazoncognito.com/oauth2/token
      jwk:
        key-set-uri: https://cognito-idp.<region>.amazonaws.com/<user-pool-id>/.well-known/jwks.json
    client:
      clientId: <client-id>

完整文章请参考:Integrate Spring Boot Resource Server with Cognito Identity Provider