配置授权和身份验证以保护您的 GraphQL API
Amazon AppSync 提供以下授权类型来保护 GraphQL API:API 密钥、Lambda、IAM、OpenID Connect 和 Cognito 用户池。每个选项都提供不同的安全方法:
-
API 密钥授权:控制未经身份验证的 API 的节流,提供一个简单的安全选项。
-
Lambda 授权:启用自定义授权逻辑,详细解释函数输入和输出。
-
IAM 授权:利用 Amazon 的签名版本 4 签名流程,允许通过 IAM 策略进行精细的访问控制。
-
OpenID Connect 授权:与 OIDC 兼容服务进行集成,用于用户身份验证。
-
Cognito 用户池:使用 Cognito 的用户管理特征实施基于群组的访问控制。
授权类型
您可以通过 5 种方法授权应用程序与 Amazon AppSync GraphQL API 进行交互。您可以在 Amazon AppSync API 或 CLI 调用中指定以下授权类型值之一,以指定使用哪种授权类型:
-
-
API_KEY
-
适用于使用 API 密钥。
-
-
-
AWS_LAMBDA
-
适用于使用 Amazon Lambda 函数。
-
-
-
AWS_IAM
-
适用于使用 Amazon Identity and Access Management (IAM
) 权限。
-
-
-
OPENID_CONNECT
-
适用于使用 OpenID Connect 提供程序。
-
-
-
AMAZON_COGNITO_USER_POOLS
-
适用于使用 Amazon Cognito 用户池。
-
这些基本授权类型适用于大多数开发人员。对于更高级的使用案例,您可以通过控制台、CLI 和 Amazon CloudFormation 添加其他授权模式。对于其他授权模式,Amazon AppSync 提供的授权类型采用上面列出的值(即 API_KEY
、AWS_LAMBDA
、AWS_IAM
、OPENID_CONNECT
和 AMAZON_COGNITO_USER_POOLS
)。
在将 API_KEY
、AWS_LAMBDA
或 AWS_IAM
指定为主要或默认授权类型时,您不能再次将其指定为其他授权模式之一。同样,您不能在其他授权模式中重复使用 API_KEY
、AWS_LAMBDA
或 AWS_IAM
。您可以使用多个 Amazon Cognito 用户池和 OpenID Connect 提供程序。不过,您不能在默认授权模式和任何其他授权模式之间使用重复的 Amazon Cognito 用户池或 OpenID Connect 提供程序。您可以使用相应的配置正则表达式,为 Amazon Cognito 用户池或 OpenID Connect 提供程序指定不同的客户端。
API_KEY 授权
未经身份验证的 API 比经过身份验证的 API 需要更严格的限制。一种对未经身份验证的 GraphQL 终端节点的限制进行控制的方法是使用 API 密钥。API 密钥是应用程序中的硬编码值,在您创建未经身份验证的 GraphQL 终端节点时,由 Amazon AppSync 服务生成该值。您可以通过控制台或 CLI 轮换 API 密钥,或者按照 Amazon AppSync API reference 轮换 API 密钥。
API 密钥可配置为最多 365 天,并且您可以将现有到期日期再延长多达 365 天(从到期日期当天开始)。建议将 API 密钥用于开发目的或可以安全公开公有 API 的使用案例。
在客户端,通过标头 x-api-key
指定 API 密钥。
例如,如果您的 API_KEY
为 'ABC123'
,则您可以通过 curl
发送 GraphQL 查询,如下所示:
$ curl -XPOST -H "Content-Type:application/graphql" -H "x-api-key:ABC123" -d '{ "query": "query { movies { id } }" }' https://YOURAPPSYNCENDPOINT/graphql
AWS_LAMBDA 授权
您可以使用 Amazon Lambda 函数实施自己的 API 授权逻辑。您可以将 Lambda 函数用于主要或辅助授权者,但每个 API 只能有一个 Lambda 授权函数。在使用 Lambda 函数进行授权时,以下限制适用:
-
如果 API 启用了
AWS_LAMBDA
和AWS_IAM
授权模式,则不能将 SigV4 签名作为AWS_LAMBDA
授权令牌。 -
如果 API 启用了
AWS_LAMBDA
和OPENID_CONNECT
授权模式或AMAZON_COGNITO_USER_POOLS
授权模式,则不能将 OIDC 令牌作为AWS_LAMBDA
授权令牌。请注意,OIDC 令牌可以采用持有者方案。 -
Lambda 函数不能为解析器返回超过 5MB 的上下文数据。
例如,如果您的授权令牌是 'ABC123'
,您可以通过 curl 发送 GraphQL 查询,如下所示:
$ curl -XPOST -H "Content-Type:application/graphql" -H "Authorization:ABC123" -d '{ "query": "query { movies { id } }" }' https://YOURAPPSYNCENDPOINT/graphql
Lambda 函数是在每个查询或变更之前调用的。可以根据 API ID 和身份验证令牌缓存返回值。默认情况下,不会开启缓存,但可以在 API 级别启用该功能,或者在函数的返回值中设置 ttlOverride
值以启用该功能。
如果需要,可以指定一个正则表达式,以在调用函数之前验证授权令牌。这些正则表达式用于在调用函数之前验证授权令牌格式是否正确。将自动拒绝使用的令牌与该正则表达式不匹配的任何请求。
用于授权的 Lambda 函数需要应用 appsync.amazonaws.com
主体策略,以允许 Amazon AppSync 调用这些函数。该操作是在 Amazon AppSync 控制台中自动完成的;Amazon AppSync 控制台不会删除该策略。有关将策略附加到 Lambda 函数的更多信息,请参阅《Amazon Lambda 开发人员指南》中的基于资源的策略。
您指定的 Lambda 函数将收到具有以下形状的事件:
{ "authorizationToken": "ExampleAUTHtoken123123123", "requestContext": { "apiId": "aaaaaa123123123example123", "accountId": "111122223333", "requestId": "f4081827-1111-4444-5555-5cf4695f339f", "queryString": "mutation CreateEvent {...}\n\nquery MyQuery {...}\n", "operationName": "MyQuery", "variables": {} } "requestHeaders": {
application request headers
} }
event
对象包含从应用程序客户端到 Amazon AppSync 的请求中发送的标头。
授权函数必须至少返回 isAuthorized
,这是一个布尔值,用于指示是否对请求进行了授权。Amazon AppSync 识别从 Lambda 授权函数中返回的以下键:
注意
WebSocket 连接操作的 requestContext
中 operationName
的值由 Amazon AppSync 设置为“DeepDish:Connect
”。
isAuthorized
(布尔值,必需)-
一个布尔值,用于指示是否授权
authorizationToken
中的值调用 GraphQL API。如果该值为 true,则继续执行 GraphQL API。如果该值为 false,则会引发
UnauthorizedException
。 deniedFields
(字符串列表,可选)-
强制更改为
null
的值列表,即使从解析器中返回值也是如此。每个项目是
arn:aws:appsync:
格式的完全限定字段 ARN,或者是us-east-1
:111122223333
:apis/GraphQLApiId
/types/TypeName
/fields/FieldName
缩写格式。如果两个 API 共享 Lambda 函数授权者,并且两个 API 的通用类型和字段之间可能存在歧义,应使用完整 ARN 格式。TypeName
.FieldName
resolverContext
(JSON 对象,可选)-
在解析器模板中显示为
$ctx.identity.resolverContext
的 JSON 对象。例如,如果解析器返回以下结构:{ "isAuthorized":true "resolverContext": { "banana":"very yellow", "apple":"very green" } }
解析器模板中的
ctx.identity.resolverContext.apple
值将为 "very green
"。resolverContext
对象仅支持键值对。不支持嵌套的键。警告
该 JSON 对象的总大小不能超过 5MB。
ttlOverride
(整数,可选)-
应缓存响应的秒数。如果未返回任何值,则使用 API 中的值。如果为 0,则不会缓存响应。
Lambda 授权者的超时为 10 秒。我们建议设计的函数尽可能在最短的时间内执行,以提高 API 的性能。
多个 Amazon AppSync API 可以共享单个身份验证 Lambda 函数。不允许跨账户授权者使用。
在多个 API 之间共享授权函数时,请注意缩写的字段名称 (
) 可能会无意中隐藏字段。要消除 typename
.fieldname
deniedFields
中的字段的歧义,您可以使用 arn:aws:appsync:
格式指定明确的字段 ARN。region
:accountId
:apis/GraphQLApiId
/types/typeName
/fields/fieldName
要在 Amazon AppSync 中添加 Lambda 函数以作为默认授权模式,请执行以下操作:
以下示例介绍了一个 Lambda 函数,以说明 Lambda 函数在作为 Amazon AppSync 授权机制时可能具有的各种身份验证和失败状态:
def handler(event, context): # This is the authorization token passed by the client token = event.get('authorizationToken') # If a lambda authorizer throws an exception, it will be treated as unauthorized. if 'Fail' in token: raise Exception('Purposefully thrown exception in Lambda Authorizer.') if 'Authorized' in token and 'ReturnContext' in token: return { 'isAuthorized': True, 'resolverContext': { 'key': 'value' } } # Authorized with no f if 'Authorized' in token: return { 'isAuthorized': True } # Partial authorization if 'Partial' in token: return { 'isAuthorized': True, 'deniedFields':['user.favoriteColor'] } if 'NeverCache' in token: return { 'isAuthorized': True, 'ttlOverride': 0 } if 'Unauthorized' in token: return { 'isAuthorized': False } # if nothing is returned, then the authorization fails. return {}
规避 SigV4 和 OIDC 令牌授权限制
可以使用以下方法规避启用某些授权模式时无法将 SigV4 签名或 OIDC 令牌作为 Lambda 授权令牌的问题。
如果要在为 Amazon AppSync API 启用了 AWS_IAM
和 AWS_LAMBDA
授权模式时将 SigV4 签名作为 Lambda 授权令牌,请执行以下操作:
-
要创建新的 Lambda 授权令牌,请在 SigV4 签名中添加随机后缀和/或前缀。
-
要检索原始 SigV4 签名,请从 Lambda 授权令牌中删除随机前缀和/或后缀以更新您的 Lambda 函数。然后,使用原始 SigV4 签名进行身份验证。
如果要在为 Amazon AppSync API 启用了 OPENID_CONNECT
授权模式或 AMAZON_COGNITO_USER_POOLS
和 AWS_LAMBDA
授权模式时将 OIDC 令牌作为 Lambda 授权令牌,请执行以下操作:
-
要创建新的 Lambda 授权令牌,请在 OIDC 令牌中添加随机后缀和/或前缀。Lambda 授权令牌不应包含持有者方案前缀。
-
要检索原始 OIDC 令牌,请从 Lambda 授权令牌中删除随机前缀和/或后缀以更新您的 Lambda 函数。然后,使用原始 OIDC 令牌进行身份验证。
AWS_IAM 授权
该授权类型对 GraphQL API 强制执行 Amazon 签名版本 4 签名过程。您可以将 Identity and Access Management (IAM
如果您希望有访问权限的角色执行所有数据操作:
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "appsync:GraphQL" ], "Resource": [ "arn:aws:appsync:us-west-2:123456789012:apis/YourGraphQLApiId/*" ] } ] }
您可以在 AppSync 控制台的主要 API 列表页面中您的 API 名称正下方找到 YourGraphQLApiId
。或者,您也可以使用 CLI 检索它:aws appsync list-graphql-apis
如果您只想限制对某些 GraphQL 操作的访问,您可以对根 Query
、Mutation
和 Subscription
字段执行此操作。
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "appsync:GraphQL" ], "Resource": [ "arn:aws:appsync:us-west-2:123456789012:apis/YourGraphQLApiId/types/Query/fields/<Field-1>", "arn:aws:appsync:us-west-2:123456789012:apis/YourGraphQLApiId/types/Query/fields/<Field-2>", "arn:aws:appsync:us-west-2:123456789012:apis/YourGraphQLApiId/types/Mutation/fields/<Field-1>", "arn:aws:appsync:us-west-2:123456789012:apis/YourGraphQLApiId/types/Subscription/fields/<Field-1>" ] } ] }
例如,假设您具有以下架构,并且您想要限制能够获取所有文章的访问权限:
schema { query: Query mutation: Mutation } type Query { posts:[Post!]! } type Mutation { addPost(id:ID!, title:String!):Post! }
角色的相应 IAM 策略(例如,您可以将其附加到 Amazon Cognito 身份池)将如下所示:
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "appsync:GraphQL" ], "Resource": [ "arn:aws:appsync:us-west-2:123456789012:apis/YourGraphQLApiId/types/Query/fields/posts" ] } ] }
OPENID_CONNECT 授权
该授权类型强制实施 OIDC 兼容服务提供的 OpenID Connect
颁发者 URL 是您需要向 Amazon AppSync 提供的唯一配置值(例如 https://auth.example.com
)。该 URL 必须可以通过 HTTPS 进行寻址。AmazonAppSync 将 /.well-known/openid-configuration
附加到颁发者 URL 中,并根据 OpenID Connect Discoveryhttps://auth.example.com/.well-known/openid-configuration
中查找 OpenID 配置。它预期会在此 URL 处检索 RFC5785jwks_uri
键,它指向具有签名密钥的 JSON Web 密钥集 (JWKS) 文档。AmazonAppSync 要求 JWKS 包含 JSON 字段 kty
和 kid
。
Amazon AppSync 支持多种签名算法。
签名算法 |
---|
RS256 |
RS384 |
RS512 |
PS256 |
PS384 |
PS512 |
HS256 |
HS384 |
HS512 |
ES256 |
ES384 |
ES512 |
我们建议您使用 RSA 算法。提供程序颁发的令牌必须包括颁发令牌的时间 (iat
),并可能包含对其进行身份验证的时间 (auth_time
)。您可以在 OpenID Connect 配置中为颁发时间 (iatTTL
) 和身份验证时间 (authTTL
) 提供 TTL 值以进行额外的验证。如果您的提供程序授权多个应用程序,还可以提供一个用于按客户端 ID 进行授权的正则表达式 (clientId
)。在 OpenID Connect 配置中存在 clientId
时,Amazon AppSync 通过以下方法验证该声明:要求 clientId
与令牌中的 aud
或 azp
声明匹配。
要验证多个客户端 ID,请使用管道运算符(“|”),它是正则表达式中的“or”。例如,如果您的 OIDC 应用程序有四个客户端分别具有客户端 ID 0A1S2D、1F4G9H、1J6L4B、6GS5MG,要仅对前三个客户端 ID 进行验证,则可以在客户端 ID 字段中放置 1F4G9H|1J6L4B|6GS5MG。
AMAZON_COGNITO_USER_POOLS 授权
该授权类型强制实施 Amazon Cognito 用户池提供的 OIDC 令牌。您的应用程序可以利用您的用户池和其他 Amazon 账户用户池中的用户和组,并将用户和组与 GraphQL 字段关联以控制访问。
在使用 Amazon Cognito 用户池时,您可以创建用户所属的组。该信息是在 JWT 令牌中编码的,在发送 GraphQL 操作时,您的应用程序通过授权标头将其发送到 Amazon AppSync。您可以在架构上使用 GraphQL 指令以控制哪些组可以对字段调用哪些解析器,从而向客户提供更受控制的访问。
例如,假设您具有以下 GraphQL 架构:
schema { query: Query mutation: Mutation } type Query { posts:[Post!]! } type Mutation { addPost(id:ID!, title:String!):Post! } ...
如果您在 Amazon Cognito 用户池中具有两个组(bloggers 和 readers),并希望限制读者以使他们无法添加新条目,则架构应如下所示:
schema { query: Query mutation: Mutation }
type Query { posts:[Post!]! @aws_auth(cognito_groups: ["Bloggers", "Readers"]) } type Mutation { addPost(id:ID!, title:String!):Post! @aws_auth(cognito_groups: ["Bloggers"]) } ...
请注意,如果您想要将访问默认设为特定的授予或拒绝策略,则可以忽略 @aws_auth
指令。当您通过控制台或以下 CLI 命令创建 GraphQL API 时,您可以在用户池配置中指定授予或拒绝策略。
$ aws appsync --region us-west-2 create-graphql-api --authentication-type AMAZON_COGNITO_USER_POOLS --name userpoolstest --user-pool-config '{ "userPoolId":"test", "defaultEffect":"ALLOW", "awsRegion":"us-west-2"}'
使用其他授权模式
在添加其他授权模式时,您可以直接在 Amazon AppSync GraphQL API 级别配置授权设置(即,您可以直接在 GraphqlApi
对象上配置的 authenticationType
字段),并且它作为架构上的默认值。这意味着任何没有特定指令的类型都必须通过 API 级别授权设置。
在架构级别,您可以使用架构上的指令指定其他授权模式。您可以在架构中的各个字段上指定授权模式。例如,对于 API_KEY
授权,您将在架构对象类型定义/字段上使用 @aws_api_key
。架构字段和对象类型定义支持以下指令:
-
@aws_api_key
- 指定字段是API_KEY
授权的。 -
@aws_iam
- 指定字段是AWS_IAM
授权的。 -
@aws_oidc
- 指定字段是OPENID_CONNECT
授权的。 -
@aws_cognito_user_pools
- 指定字段是AMAZON_COGNITO_USER_POOLS
授权的。 -
@aws_lambda
- 指定字段是AWS_LAMBDA
授权的。
您不能将 @aws_auth
指令与其他授权模式一起使用。@aws_auth
仅适用于 AMAZON_COGNITO_USER_POOLS
授权的上下文,没有其他授权模式。但是,您可以使用 @aws_cognito_user_pools
指令代替 @aws_auth
指令,使用相同的参数。两者之间的主要区别在于您可以在任何字段和对象类型定义上指定 @aws_cognito_user_pools
。
要了解其他授权模式如何工作以及如何在架构上指定它们,我们来看一下以下架构:
schema { query: Query mutation: Mutation } type Query { getPost(id: ID): Post getAllPosts(): [Post] @aws_api_key } type Mutation { addPost( id: ID! author: String! title: String! content: String! url: String! ): Post! } type Post @aws_api_key @aws_iam { id: ID! author: String title: String content: String url: String ups: Int! downs: Int! version: Int! } ...
对于该架构,假设 AWS_IAM
是 Amazon AppSync GraphQL API 上的默认授权类型。这意味着使用 AWS_IAM
保护没有指令的字段。例如,Query
类型上的 getPost
字段就是这种情况。架构指令使您可以使用多种授权模式。例如,您可以将 API_KEY
配置为 Amazon AppSync GraphQL API 上的其他授权模式,并且可以使用 @aws_api_key
指令标记字段(例如,该示例中的 getAllPosts
)。指令在字段级别工作,因此您也需要授予 API_KEY
访问 Post
类型的权限。您可以通过使用指令标记 Post
类型中的每个字段,或使用 @aws_api_key
指令标记 Post
类型来执行此操作。
要进一步限制对 Post
类型中的字段的访问,可以对 Post
类型中的各个字段使用指令,如下所示。
例如,您可以将 restrictedContent
字段添加到 Post
类型并使用 @aws_iam
指令限制对它的访问。 AWS_IAM
经过身份验证的请求可以访问 restrictedContent
,但是,API_KEY
请求将无法访问它。
type Post @aws_api_key @aws_iam{ id: ID! author: String title: String content: String url: String ups: Int! downs: Int! version: Int! restrictedContent: String! @aws_iam } ...
精细访问控制
上述信息演示了如何限制或授权对某些 GraphQL 字段的访问权限。如果您想根据某些条件(例如,根据进行调用的用户以及用户是否拥有数据)来设置对数据的访问控制,则可以使用解析器中的映射模板。您还可以执行更复杂的业务逻辑,筛选信息中介绍了相关内容。
本节介绍了如何使用 DynamoDB 解析器映射模板设置数据的访问控制。
在继续操作之前,如果您不熟悉 Amazon AppSync 中的映射模板,您可能需要查看解析器映射模板参考和 DynamoDB 解析器映射模板参考。
在使用 DynamoDB 的以下示例中,假设您使用前面的博客文章架构,并且仅允许创建博客的用户对其进行编辑。评估过程将是用户在应用程序中获得凭证(例如,使用 Amazon Cognito 用户池),然后将这些凭证作为 GraphQL 操作的一部分进行传递。然后,映射模板将替换条件语句中凭证(如用户名)的值,该值随后将与数据库中的值进行比较。

要添加此功能,请添加 GraphQL 字段 editPost
,如下所示:
schema { query: Query mutation: Mutation } type Query { posts:[Post!]! } type Mutation { editPost(id:ID!, title:String, content:String):Post addPost(id:ID!, title:String!):Post! } ...
editPost
的解析器映射模板(本节末尾的示例中所示)需要针对您的数据存储执行逻辑检查,以仅允许创建文章的用户对文章进行编辑。由于这是一个编辑操作,因此,它对应于 DynamoDB 中的 UpdateItem
。您可以先执行条件检查,然后再执行此操作,同时使用传递的上下文进行用户身份验证。这些信息存储在一个 Identity
对象中,该对象具有以下值:
{ "accountId" : "12321434323", "cognitoIdentityPoolId" : "", "cognitoIdentityId" : "", "sourceIP" : "", "caller" : "ThisistheprincipalARN", "username" : "username", "userArn" : "Sameasabove" }
要在 DynamoDB UpdateItem
调用中使用该对象,您需要在表中存储用户身份信息以进行比较。首先,您的 addPost
变更需要存储创建者。其次,您的 editPost
变更需要先执行条件检查,然后才能更新。
下面是 addPost
的解析器代码示例,用于将用户身份存储为 Author
列:
import { util, Context } from '@aws-appsync/utils'; import { put } from '@aws-appsync/utils/dynamodb'; export function request(ctx) { const { id: postId, ...item } = ctx.args; return put({ key: { postId }, item: { ...item, Author: ctx.identity.username }, condition: { postId: { attributeExists: false } }, }); } export const response = (ctx) => ctx.result;
请注意,Author
属性通过 Identity
对象填充,该对象来自应用程序。
最后,此处的示例介绍了 editPost
的解析器代码,此代码仅当请求来自创建博客文章的用户时,才更新该博客文章的内容。
import { util, Context } from '@aws-appsync/utils'; import { put } from '@aws-appsync/utils/dynamodb'; export function request(ctx) { const { id, ...item } = ctx.args; return put({ key: { id }, item, condition: { author: { contains: ctx.identity.username } }, }); } export const response = (ctx) => ctx.result;
此示例使用 PutItem
覆盖所有值,而不是使用 UpdateItem
,但同样的概念适用于 condition
语句块。
筛选信息
有时,在成功写入或读取数据来源时,您可能无法控制来自数据来源的响应,但又不想向客户端发送不必要的信息。在这些情况下,您可以使用响应映射模板筛选信息。
例如,假设您的博客文章 DynamoDB 表上没有相应的索引(例如 Author
上的索引),您可以使用以下解析器:
import { util, Context } from '@aws-appsync/utils'; import { get } from '@aws-appsync/utils/dynamodb'; export function request(ctx) { return get({ key: { ctx.args.id } }); } export function response(ctx) { if (ctx.result.author === ctx.identity.username) { return ctx.result; } return null; }
即使调用方不是创建博客文章的作者,请求处理程序也会获取该项目。为了防止它返回所有数据,响应处理程序会进行检查,以确保调用方与项目的作者匹配。如果调用方与此检查不匹配,则只返回 Null 响应。
数据来源访问
Amazon AppSync 使用 Identity and Access Management (IAM
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": "appsync.amazonaws.com" }, "Action": "sts:AssumeRole" } ] }
需要将角色的访问策略缩小为仅具有对必要的最少资源集进行操作的权限,这一点非常重要。使用 AppSync 控制台创建数据来源并创建角色时,将自动为您完成此操作。不过,在使用 IAM 控制台中的内置示例模板在 Amazon AppSync 控制台外部创建角色时,权限不会自动缩小到资源范围,您应该在将应用程序部署到生产环境之前执行该操作。