如何在不使用 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
场景:
我将在任何 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