本文為英文版的機器翻譯版本,如內容有任何歧義或不一致之處,概以英文版為準。
用於保護請求和回應的存取控制使用案例
在安全區段中,您已了解保護 的不同授權模式,API並已針對精細分割授權機制進行介紹,以了解概念和流程。由於 AWS AppSync 可讓您透過使用 GraphQL Resolver 映射範本 對資料執行邏輯完整操作,因此您可以使用使用者身分、條件和資料注入的組合,以非常靈活的方式保護讀取或寫入時的資料。
如果您不熟悉編輯 AWS AppSync 解析程式,請檢閱程式設計指南 。
概觀
傳統上,授予系統中資料的存取權是透過存取控制矩陣
AWS AppSync 使用您帳戶中的資源,並將身分 (使用者/角色) 資訊執行到 GraphQL 請求和回應中,作為內容物件 ,您可以在解析器中使用。這表示您可依據解析程式邏輯,將權限適當地授與寫入或讀取操作。如果此邏輯處於資源層級,例如只有特定具名使用者或群組可以讀取/寫入特定資料庫資料列,則必須儲存「授權中繼資料」。 AWS AppSync 不會儲存任何資料,因此您必須將此授權中繼資料與 資源一起存放,才能計算許可。授權中繼資料通常是 DynamoDB 表之中的屬性 (欄),例如擁有者或使用者/群組清單。例如其中可能有讀取器及寫入器屬性。
大致而言,這表示若您由資料來源讀取個別項目,就會在解析程式由資料來源讀取之後,於回應範本執行條件式 #if () ... #end
陳述式。檢查通常會使用 $context.identity
之中的使用者或群組值,依據讀取操作傳回的授權中繼資料進行成員檢查。對於多筆記錄,例如由資料表 Scan
或 Query
傳回的清單,您將使用類似的使用者或群組值,將條件檢查做為操作的一部分傳送至資料來源。
同樣地,寫入資料時,您會將條件式陳述式套用至動作 (例如 PutItem
或 UpdateItem
),了解進行變動的使用者或群組是否具有許可。條件式將再次多次使用 $context.identity
之中的值,與該項資源的授權中繼資料進行比較。對於要求及回應範本,您也可使用用戶端的自訂標頭執行驗證檢查。
讀取資料
如前所述,執行檢查的授權中繼資料必須與資源儲存,或傳送至 GraphQL 要求 (身分、標頭等等)。為了示範,假設您有下列的 DynamoDB 表格:
主索引鍵為 id
,要存取的資料為 Data
。其他資料欄是您可以執行以進行授權的檢查範例。 Owner
會是String
一段時間PeopleCanAccess
,並且GroupsCanAccess
會String Sets
如 DynamoDB 的解析器映射範本參考中所述。
在解析程式映射範本概觀之中的圖會顯示回應範本不僅包含內容物件,也包含資料來源結果。對個別項目的 GraphQL 查詢而言,您可使用回應範本檢查使用者是否允許檢視這些結果,否則將傳回授權錯誤訊息。這有時稱為「授權篩選」。對使用掃描或查詢的 GraphQL 查詢傳回清單而言,效能較高的作法是在要求範本執行檢查,並僅於滿足授權條件時傳回資料。實作之後將:
-
GetItem - 個別記錄的授權檢查。使用
#if() ... #end
陳述式完成。 -
掃描/查詢操作 - 授權檢查為
"filter":{"expression":...}
陳述式。一般檢查為對等 (attribute = :input
) 或檢查值是否在清單之中 (contains(attribute, :input)
)。
在 #2 之中,兩種陳述式的 attribute
都代表表格之中記錄的欄名稱,例如以上範例中的 Owner
。您可利用 #
簽署和使用 "expressionNames":{...}
為此設定別名,但並非必要。:input
是您要與資料庫屬性比較值的參考,該屬性將於 "expressionValues":{...}
定義。您可參考以下各項範例。
使用案例:擁有者可以讀取
使用以上表格,若您只想傳回資料,了解 Owner ==
Nadia
是否用於個別讀取操作 (GetItem
),您的範本可能會像這樣:
#if($context.result["Owner"] == $context.identity.username) $utils.toJson($context.result) #else $utils.unauthorized() #end
這裡提出兩點注意事項,在剩餘章節中會用到。首先,如果使用 Amazon Cognito 使用者集區,則檢查會使用易記的使用者註冊名稱$context.identity.username
,如果使用 IAM (包括 Amazon Cognito 聯合身分),檢查將是使用者身分。還有其他值要存放給擁有者,例如唯一的「Amazon Cognito 身分」值,在聯合來自多個位置的登入時很有用,您應該檢閱解析程式映射範本內容參考 中可用的選項。
其次,條件式其他檢查會使用 進行回應$util.unauthorized()
完全是選擇性的,但建議在設計 GraphQL 時作為最佳實務API。
使用案例:硬碼特定存取
// This checks if the user is part of the Admin group and makes the call #foreach($group in $context.identity.claims.get("cognito:groups")) #if($group == "Admin") #set($inCognitoGroup = true) #end #end #if($inCognitoGroup) { "version" : "2017-02-28", "operation" : "UpdateItem", "key" : { "id" : $util.dynamodb.toDynamoDBJson($ctx.args.id) }, "attributeValues" : { "owner" : $util.dynamodb.toDynamoDBJson($context.identity.username) #foreach( $entry in $context.arguments.entrySet() ) ,"${entry.key}" : $util.dynamodb.toDynamoDBJson($entry.value) #end } } #else $utils.unauthorized() #end
使用案例:篩選結果清單
在前一項範例之中,您可在 $context.result
傳回單一項目時對其執行檢查,不過像是掃描等部分操作,將在 $context.result.items
傳回多個項目;您在此需要執行驗證篩選,並僅傳回使用者允許檢視的結果。假設 Owner
欄位在記錄上設定了這次的 Amazon Cognito IdentityID,則您可以使用下列回應映射範本來篩選,僅顯示使用者擁有的記錄:
#set($myResults = []) #foreach($item in $context.result.items) ##For userpools use $context.identity.username instead #if($item.Owner == $context.identity.cognitoIdentityId) #set($added = $myResults.add($item)) #end #end $utils.toJson($myResults)
使用案例:多人可以讀取
另一種熱門的授權選擇,就是允許一群人讀取資料。在以下範例中,"filter":{"expression":...}
僅傳回表格掃描的值 (若使用者執行的 GraphQL 查詢列於 PeopleCanAccess
集之中)。
{ "version" : "2017-02-28", "operation" : "Scan", "limit": #if(${context.arguments.count}) $util.toJson($context.arguments.count) #else 20 #end, "nextToken": #if(${context.arguments.nextToken}) $util.toJson($context.arguments.nextToken) #else null #end, "filter":{ "expression": "contains(#peopleCanAccess, :value)", "expressionNames": { "#peopleCanAccess": "peopleCanAccess" }, "expressionValues": { ":value": $util.dynamodb.toDynamoDBJson($context.identity.username) } } }
使用案例:群組可以讀取
類似於上一項使用案例,可能只有單一或更多群組的人,有權讀取資料庫之中的特定項目。"expression":
"contains()"
操作的使用方式類似,不過這是所有群組的邏輯 OR,使用者可能是其中的一部分,需要在設定成員資格時加以考量。在此案例中,我們針對使用者所屬的各個群組建立以下 $expression
陳述式,然後將其傳送至篩選器:
#set($expression = "") #set($expressionValues = {}) #foreach($group in $context.identity.claims.get("cognito:groups")) #set( $expression = "${expression} contains(groupsCanAccess, :var$foreach.count )" ) #set( $val = {}) #set( $test = $val.put("S", $group)) #set( $values = $expressionValues.put(":var$foreach.count", $val)) #if ( $foreach.hasNext ) #set( $expression = "${expression} OR" ) #end #end { "version" : "2017-02-28", "operation" : "Scan", "limit": #if(${context.arguments.count}) $util.toJson($context.arguments.count) #else 20 #end, "nextToken": #if(${context.arguments.nextToken}) $util.toJson($context.arguments.nextToken) #else null #end, "filter":{ "expression": "$expression", "expressionValues": $utils.toJson($expressionValues) } }
寫入資料
在變動寫入資料一定是在要求映射範本進行控制。就 DynamoDB 資料來源而言,關鍵在於使用適當的 "condition":{"expression"...}"
,依據該表格之中的授權中繼資料執行驗證。在安全性中,我們提供範例讓您用來檢查資料表中的 Author
欄位。本節使用案例將探索更多使用案例。
使用案例:多個擁有者
使用之前的範例表格圖示,假設 PeopleCanAccess
清單
{ "version" : "2017-02-28", "operation" : "UpdateItem", "key" : { "id" : $util.dynamodb.toDynamoDBJson($ctx.args.id) }, "update" : { "expression" : "SET meta = :meta", "expressionValues": { ":meta" : $util.dynamodb.toDynamoDBJson($ctx.args.meta) } }, "condition" : { "expression" : "contains(Owner,:expectedOwner)", "expressionValues" : { ":expectedOwner" : $util.dynamodb.toDynamoDBJson($context.identity.username) } } }
使用案例:群組可以建立新的記錄
#set($expression = "") #set($expressionValues = {}) #foreach($group in $context.identity.claims.get("cognito:groups")) #set( $expression = "${expression} contains(groupsCanAccess, :var$foreach.count )" ) #set( $val = {}) #set( $test = $val.put("S", $group)) #set( $values = $expressionValues.put(":var$foreach.count", $val)) #if ( $foreach.hasNext ) #set( $expression = "${expression} OR" ) #end #end { "version" : "2017-02-28", "operation" : "PutItem", "key" : { ## If your table's hash key is not named 'id', update it here. ** "id" : $util.dynamodb.toDynamoDBJson($ctx.args.id) ## If your table has a sort key, add it as an item here. ** }, "attributeValues" : { ## Add an item for each field you would like to store to Amazon DynamoDB. ** "title" : $util.dynamodb.toDynamoDBJson($ctx.args.title), "content": $util.dynamodb.toDynamoDBJson($ctx.args.content), "owner": $util.dynamodb.toDynamoDBJson($context.identity.username) }, "condition" : { "expression": $util.toJson("attribute_not_exists(id) AND $expression"), "expressionValues": $utils.toJson($expressionValues) } }
使用案例:群組可以更新現有記錄
#set($expression = "") #set($expressionValues = {}) #foreach($group in $context.identity.claims.get("cognito:groups")) #set( $expression = "${expression} contains(groupsCanAccess, :var$foreach.count )" ) #set( $val = {}) #set( $test = $val.put("S", $group)) #set( $values = $expressionValues.put(":var$foreach.count", $val)) #if ( $foreach.hasNext ) #set( $expression = "${expression} OR" ) #end #end { "version" : "2017-02-28", "operation" : "UpdateItem", "key" : { "id" : $util.dynamodb.toDynamoDBJson($ctx.args.id) }, "update":{ "expression" : "SET title = :title, content = :content", "expressionValues": { ":title" : $util.dynamodb.toDynamoDBJson($ctx.args.title), ":content" : $util.dynamodb.toDynamoDBJson($ctx.args.content) } }, "condition" : { "expression": $util.toJson($expression), "expressionValues": $utils.toJson($expressionValues) } }
公有和私有記錄
您也可以使用條件式篩選器,選擇將資料標示為公開、私人或其他布林檢查。並於之後合併為回應範本之中授權篩選的一部分。此項檢查適合用於暫時隱藏資料,或由檢視之中移除資料,不必嘗試控制群組成員資格。
例如,假設您在名為 public
的 DynamoDB 表格,以 yes
或 no
值在各個項目新增屬性。下列回應範本可在GetItem
通話中使用,只有在使用者位於具有存取權的群組中,且資料標示為公開AND時,才會顯示資料:
#set($permissions = $context.result.GroupsCanAccess) #set($claimPermissions = $context.identity.claims.get("cognito:groups")) #foreach($per in $permissions) #foreach($cgroups in $claimPermissions) #if($cgroups == $per) #set($hasPermission = true) #end #end #end #if($hasPermission && $context.result.public == 'yes') $utils.toJson($context.result) #else $utils.unauthorized() #end
以上程式碼也可使用邏輯 OR (||
),在使用者擁有權限或記錄為公開的情況下能夠讀取記錄:
#if($hasPermission || $context.result.public == 'yes') $utils.toJson($context.result) #else $utils.unauthorized() #end
一般而言,您在執行授權檢查時,運算子 ==
、!=
、&&
及 ||
都相當實用。
即時資料
您可在用戶端訂閱時,使用本文之前介紹的相同技巧,將精細分級的存取控制套用至 GraphQL 訂閱。您將解析程式附加至訂閱欄位,在此由資料來源查詢資料,並於要求或回應映射範本時執行條件式邏輯。只要資料結構符合 GraphQL 訂閱之中傳回的類型,您也可以傳回其他資料給用戶端,例如訂閱的初始結果。
使用案例:使用者只能訂閱特定對話
這是常見的即時資料使用案例,其中 GraphQL 訂閱正在建構訊息或私人交談應用程式。建立具有多位使用者的交談應用程式時,可能在兩位或多位使用者之間產生對話。這些使用者可能會聚集成為私人或公開的「聊天室」。在這種情況下,您可能只想授權使用者訂閱其有權存取的對話 (一對一或群組之中)。以下範例示範單一使用者向他人傳送私人訊息的簡單使用案例。此設定有兩個 Amazon DynamoDB 資料表:
-
訊息表格:(主索引鍵)
toUser
、(排序索引鍵)id
-
權限表格:(主索引鍵)
username
訊息表格儲存透過 GraphQL 變動傳送的實際訊息。權限表格會由 GraphQL 訂閱檢查,在用戶端連線時進行授權。以下範例假設您使用下列 GraphQL 結構描述:
input CreateUserPermissionsInput { user: String! isAuthorizedForSubscriptions: Boolean } type Message { id: ID toUser: String fromUser: String content: String } type MessageConnection { items: [Message] nextToken: String } type Mutation { sendMessage(toUser: String!, content: String!): Message createUserPermissions(input: CreateUserPermissionsInput!): UserPermissions updateUserPermissions(input: UpdateUserPermissionInput!): UserPermissions } type Query { getMyMessages(first: Int, after: String): MessageConnection getUserPermissions(user: String!): UserPermissions } type Subscription { newMessage(toUser: String!): Message @aws_subscribe(mutations: ["sendMessage"]) } input UpdateUserPermissionInput { user: String! isAuthorizedForSubscriptions: Boolean } type UserPermissions { user: String isAuthorizedForSubscriptions: Boolean } schema { query: Query mutation: Mutation subscription: Subscription }
下面createUserPermissions()
未涵蓋一些標準操作,例如 ,以說明訂閱解析程式,但 是 DynamoDB 解析程式的標準實作。我們將重點放在訂閱授權流程與解析程式。使用下列要求範本,讓使用者將訊息傳送給其他使用者,將解析程式連接至 sendMessage()
欄位,然後選取 Messages (訊息) 資料表資料來源:
{ "version" : "2017-02-28", "operation" : "PutItem", "key" : { "toUser" : $util.dynamodb.toDynamoDBJson($ctx.args.toUser), "id" : $util.dynamodb.toDynamoDBJson($util.autoId()) }, "attributeValues" : { "fromUser" : $util.dynamodb.toDynamoDBJson($context.identity.username), "content" : $util.dynamodb.toDynamoDBJson($ctx.args.content), } }
在此範例中,我們使用 $context.identity.username
. 這會傳回 AWS Identity and Access Management 或 Amazon Cognito 使用者的使用者資訊。回應範本為簡單的 $util.toJson($ctx.result)
傳遞。儲存後回到結構描述頁面。然後連接解析程式以用於 newMessage()
訂閱,使用許可資料表做為資料來源,以及下列要求映射範本:
{ "version": "2018-05-29", "operation": "GetItem", "key": { "username": $util.dynamodb.toDynamoDBJson($ctx.identity.username), }, }
之後請使用下列回應映射範本,以許可資料表的資料執行授權檢查:
#if(! ${context.result}) $utils.unauthorized() #elseif(${context.identity.username} != ${context.arguments.toUser}) $utils.unauthorized() #elseif(! ${context.result.isAuthorizedForSubscriptions}) $utils.unauthorized() #else ##User is authorized, but we return null to continue null #end
在此情況下,您會進行三項授權檢查。第一項檢查確保結果已返回。第二項檢查確保使用者並未訂閱其他人的訊息。第三個 會檢查isAuthorizedForSubscriptions
儲存為 的 DynamoDB 屬性,以確保使用者能夠訂閱任何欄位BOOL
。
若要測試問題,您可以使用 Amazon Cognito 使用者集區和名為「Nadia」的使用者登入 AWS AppSync 主控台,然後執行下列 GraphQL 訂閱:
subscription AuthorizedSubscription { newMessage(toUser: "Nadia") { id toUser fromUser content } }
如果在許可資料表之中,存在 Nadia
的 username
索引鍵屬性記錄,且 isAuthorizedForSubscriptions
設定為 true
,您就可以獲得成功回應。如果您在以上的 username
查詢嘗試不同 newMessage()
,就會傳回錯誤。