View a markdown version of this page

Kasus penggunaan kontrol akses untuk mengamankan permintaan dan tanggapan - AWS AppSync GraphQL

Terjemahan disediakan oleh mesin penerjemah. Jika konten terjemahan yang diberikan bertentangan dengan versi bahasa Inggris aslinya, utamakan versi bahasa Inggris.

Kasus penggunaan kontrol akses untuk mengamankan permintaan dan tanggapan

Di bagian Keamanan, Anda mempelajari tentang berbagai mode Otorisasi untuk melindungi API Anda dan pengenalan diberikan tentang mekanisme Otorisasi Berbutir Halus untuk memahami konsep dan alur. Karena AWS AppSync memungkinkan Anda untuk melakukan operasi logika penuh pada data melalui penggunaan template GraphQL Resolver Mapping, Anda dapat melindungi data saat membaca atau menulis dengan cara yang sangat fleksibel menggunakan kombinasi identitas pengguna, syarat, dan injeksi data.

Jika Anda tidak terbiasa dengan mengedit AWS AppSync Resolver, tinjau panduan Resolver Mapping Template Programming Guide pemrograman.

Gambaran umum

Pemberian akses ke data dalam sistem secara tradisional dilakukan melalui matriks kontrol akses di mana persimpangan baris (sumber daya) dan kolom (user/role) adalah izin yang diberikan.

AWS AppSync menggunakan sumber daya di akun Anda sendiri dan memasukkan informasi identitas (user/role) ke dalam permintaan dan respons GraphQL sebagai objek konteks, yang dapat Anda gunakan di resolver. Ini berarti bahwa izin dapat diberikan dengan tepat baik pada operasi tulis atau baca berdasarkan logika resolver. Jika logika ini berada pada tingkat sumber daya, misalnya hanya pengguna atau grup tertentu yang dapat read/write menggunakan baris database tertentu, maka “metadata otorisasi” itu harus disimpan. AWS AppSync tidak menyimpan data apa pun sehingga Anda harus menyimpan metadata otorisasi ini dengan sumber daya sehingga izin dapat dihitung. Metadata otorisasi biasanya merupakan atribut (kolom) dalam tabel DynamoDB, seperti pemilik atau daftar. users/groups Misalnya mungkin ada atribut Pemb aca dan Penulis.

Dari tingkat tinggi, artinya ini adalah bahwa jika Anda membaca item individual dari sumber data, Anda melakukan #if () ... #end pernyataan bersyarat dalam template respons setelah resolver membaca dari sumber data. Pemeriksaan biasanya akan menggunakan nilai pengguna atau grup $context.identity untuk pemeriksaan keanggotaan terhadap metadata otorisasi yang dikembalikan dari operasi baca. Untuk beberapa catatan, seperti daftar yang dikembalikan dari tabel Scan atauQuery, Anda akan mengirim pemeriksaan kondisi sebagai bagian dari operasi ke sumber data menggunakan nilai pengguna atau grup yang serupa.

Demikian pula saat menulis data, Anda akan menerapkan pernyataan bersyarat ke tindakan (seperti PutItem atau UpdateItem untuk melihat apakah pengguna atau grup yang membuat mutasi memiliki izin. Kondisional lagi akan berkali-kali menggunakan nilai $context.identity untuk membandingkan dengan metadata otorisasi pada sumber daya itu. Untuk template permintaan dan respons, Anda juga dapat menggunakan header khusus dari klien untuk melakukan pemeriksaan validasi.

Membaca data

Seperti yang diuraikan di atas metadata otorisasi untuk melakukan pemeriksaan harus disimpan dengan sumber daya atau diteruskan ke permintaan GraphQL (identitas, header, dll.). Untuk menunjukkan ini misalkan Anda memiliki tabel DynamoDB di bawah ini:

DynamoDB tabel dengan kolom ID, Data PeopleCanAccess, GroupsCanAccess,, dan Pemilik.

Kunci utama adalah id dan data yang akan diakses adalahData. Kolom lainnya adalah contoh pemeriksaan yang dapat Anda lakukan untuk otorisasi. Ownerakan memakan waktu lama String PeopleCanAccess dan GroupsCanAccess akan seperti yang diuraikan dalam referensi template pemetaan Resolver untuk Dynam oDB. String Sets

Dalam ik htisar template pemetaan resolver diagram menunjukkan bagaimana template respons tidak hanya berisi objek konteks tetapi juga hasil dari sumber data. Untuk kueri GraphQL dari item individual, Anda dapat menggunakan template respons untuk memeriksa apakah pengguna diizinkan untuk melihat hasil ini atau mengembalikan pesan kesalahan otorisasi. Ini kadang-kadang disebut sebagai “filter otorisasi”. Untuk kueri GraphQL yang mengembalikan daftar, menggunakan Scan atau Query, lebih berkinerja untuk melakukan pemeriksaan pada template permintaan dan mengembalikan data hanya jika kondisi otorisasi terpenuhi. Implementasinya kemudian:

  1. GetItem - pemeriksaan otorisasi untuk catatan individu. Selesai menggunakan #if() ... #end pernyataan.

  2. Scan/Query operasi - pemeriksaan otorisasi adalah "filter":{"expression":...} pernyataan. Pemeriksaan umum adalah persamaan (attribute = :input) atau memeriksa apakah suatu nilai ada dalam daftar (contains(attribute, :input)).

Dalam #2 pernyataan attribute di kedua mewakili nama kolom catatan dalam tabel, seperti Owner dalam contoh di atas. Anda dapat menamai ini dengan # tanda dan gunakan "expressionNames":{...} tetapi itu tidak wajib. Ini :input adalah referensi ke nilai yang Anda bandingkan dengan atribut database, yang akan Anda tentukan"expressionValues":{...}. Anda akan melihat contoh-contoh ini di bawah ini.

Kasus penggunaan: pemilik dapat membaca

Menggunakan tabel di atas, jika Anda hanya ingin mengembalikan data jika Owner == Nadia untuk operasi baca individu (GetItem) template Anda akan terlihat seperti:

#if($context.result["Owner"] == $context.identity.username) $utils.toJson($context.result) #else $utils.unauthorized() #end

Beberapa hal untuk disebutkan di sini yang akan digunakan kembali di bagian yang tersisa. Pertama, cek menggunakan nama pendaftaran pengguna $context.identity.username yang ramah jika kumpulan pengguna Amazon Cognito digunakan dan akan menjadi identitas pengguna jika IAM digunakan (termasuk Amazon Cognito Federated Identities). Ada nilai lain untuk disimpan untuk pemilik seperti nilai unik “identitas Amazon Cognito”, yang berguna saat menggabungkan login dari beberapa lokasi, dan Anda harus meninjau opsi yang tersedia di Referensi Konteks Template Pemetaan Resolver.

Kedua, pemeriksaan lain bersyarat yang merespons $util.unauthorized() sepenuhnya opsional tetapi direkomendasikan sebagai praktik terbaik saat merancang GraphQL API Anda.

Kasus penggunaan: akses khusus hardcode

// 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

Kasus penggunaan: memfilter daftar hasil

Dalam contoh sebelumnya Anda dapat melakukan pemeriksaan $context.result secara langsung karena mengembalikan satu item, namun beberapa operasi seperti pemindaian akan mengembalikan beberapa item di $context.result.items mana Anda perlu melakukan filter otorisasi dan hanya mengembalikan hasil yang diizinkan untuk dilihat pengguna. Misalkan Owner bidang memiliki Amazon Cognito IdentityId kali ini disetel pada catatan, Anda kemudian dapat menggunakan templat pemetaan respons berikut untuk memfilter agar hanya menampilkan catatan yang dimiliki pengguna:

#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)

Kasus penggunaan: banyak orang dapat membaca

Opsi otorisasi populer lainnya adalah memungkinkan sekelompok orang untuk dapat membaca data. Dalam contoh di bawah ini "filter":{"expression":...} hanya mengembalikan nilai dari pemindaian tabel jika pengguna yang menjalankan kueri GraphQL terdaftar dalam set forPeopleCanAccess.

{ "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) } } }

Kasus penggunaan: grup dapat membaca

Mirip dengan kasus penggunaan terakhir, mungkin hanya orang dalam satu atau lebih kelompok yang memiliki hak untuk membaca item tertentu dalam database. Penggunaan "expression": "contains()" operasi serupa namun logis-atau dari semua grup yang mungkin menjadi bagiannya pengguna yang perlu diperhitungkan dalam keanggotaan yang ditetapkan. Dalam hal ini kami membuat $expression pernyataan di bawah ini untuk setiap grup tempat pengguna berada dan kemudian meneruskannya ke filter:

#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) } }

Menulis data

Menulis data tentang mutasi selalu dikendalikan pada template pemetaan permintaan. Dalam kasus sumber data DynamoDB, kuncinya adalah menggunakan "condition":{"expression"...}" yang sesuai yang melakukan validasi terhadap metadata otorisasi dalam tabel itu. Di Keamanan, kami memberikan contoh yang dapat Anda gunakan untuk memeriksa Author bidang dalam tabel. Kasus penggunaan di bagian ini mengeksplorasi lebih banyak kasus penggunaan.

Kasus penggunaan: beberapa pemilik

Menggunakan contoh diagram tabel dari sebelumnya, misalkan PeopleCanAccess daftar

{ "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) } } }

Kasus penggunaan: grup dapat membuat catatan baru

#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) } }

Kasus penggunaan: grup dapat memperbarui catatan yang ada

#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) } }

Catatan publik dan pribadi

Dengan filter kondisional Anda juga dapat memilih untuk menandai data sebagai pemeriksaan pribadi, publik atau Boolean lainnya. Ini kemudian dapat digabungkan sebagai bagian dari filter otorisasi di dalam template respons. Menggunakan pemeriksaan ini adalah cara yang bagus untuk menyembunyikan sementara data atau menghapusnya dari tampilan tanpa mencoba mengontrol keanggotaan grup.

Misalnya Anda menambahkan atribut pada setiap item dalam tabel DynamoDB Anda yang dipanggil public dengan nilai yes atau. no Template respons berikut dapat digunakan pada GetItem panggilan untuk hanya menampilkan data jika pengguna berada dalam grup yang memiliki akses DAN jika data tersebut ditandai sebagai publik:

#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

Kode di atas juga dapat menggunakan OR (||) logis untuk memungkinkan orang membaca jika mereka memiliki izin untuk catatan atau jika itu publik:

#if($hasPermission || $context.result.public == 'yes') $utils.toJson($context.result) #else $utils.unauthorized() #end

Secara umum, Anda akan menemukan operator standar==,, !=&&, dan || membantu saat melakukan pemeriksaan otorisasi.

Real-time data

Anda dapat menerapkan Kontrol Akses Berbutir halus ke langganan GraphQL pada saat klien membuat langganan, menggunakan teknik yang sama yang dijelaskan sebelumnya dalam dokumentasi ini. Anda melampirkan resolver ke bidang langganan, di mana Anda dapat menanyakan data dari sumber data dan melakukan logika kondisional baik di template pemetaan permintaan atau respons. Anda juga dapat mengembalikan data tambahan ke klien, seperti hasil awal dari langganan, selama struktur data cocok dengan jenis yang dikembalikan dalam langganan GraphQL Anda.

Kasus penggunaan: pengguna hanya dapat berlangganan percakapan tertentu

Kasus penggunaan umum untuk data real-time dengan langganan GraphQL adalah membangun aplikasi perpesanan atau obrolan pribadi. Saat membuat aplikasi obrolan yang memiliki banyak pengguna, percakapan dapat terjadi antara dua orang atau di antara beberapa orang. Ini mungkin dikelompokkan ke dalam “kamar”, yang bersifat pribadi atau publik. Dengan demikian, Anda hanya ingin memberi otorisasi kepada pengguna untuk berlangganan percakapan (yang bisa satu lawan satu atau di antara grup) yang telah diberikan akses kepada pengguna. Untuk tujuan demonstrasi, contoh di bawah ini menunjukkan kasus penggunaan sederhana dari satu pengguna mengirim pesan pribadi ke pengguna lain. Pengaturan memiliki dua tabel Amazon DynamoDB:

  • Tabel pesan: (kunci utama)toUser, (kunci pengurutan) id

  • Tabel izin: (kunci utama) username

Tabel Pesan menyimpan pesan aktual yang dikirim melalui mutasi GraphQL. Tabel Izin diperiksa oleh langganan GraphQL untuk otorisasi pada waktu koneksi klien. Contoh di bawah ini mengasumsikan Anda menggunakan skema GraphQL berikut:

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 }

Beberapa operasi standar, seperticreateUserPermissions(), tidak tercakup di bawah ini untuk menggambarkan resolver langganan, tetapi merupakan implementasi standar dari resolver DynamoDB. Sebagai gantinya, kami akan fokus pada alur otorisasi langganan dengan resolver. Untuk mengirim pesan dari satu pengguna ke pengguna lain, lampirkan resolver ke sendMessage() bidang dan pilih sumber data tabel Pesan dengan templat permintaan berikut:

{ "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), } }

Dalam contoh ini, kami menggunakan $context.identity.username. Ini mengembalikan informasi pengguna untuk AWS Identity and Access Management atau pengguna Amazon Cognito. Template respons adalah jalan pintas sederhana dari$util.toJson($ctx.result). Simpan dan kembali ke halaman skema. Kemudian lampirkan resolver untuk newMessage() langganan, menggunakan tabel Izin sebagai sumber data dan template pemetaan permintaan berikut:

{ "version": "2018-05-29", "operation": "GetItem", "key": { "username": $util.dynamodb.toDynamoDBJson($ctx.identity.username), }, }

Kemudian gunakan template pemetaan respons berikut untuk melakukan pemeriksaan otorisasi menggunakan data dari tabel Izin:

#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

Dalam hal ini, Anda melakukan tiga pemeriksaan otorisasi. Yang pertama memastikan bahwa hasil dikembalikan. Yang kedua memastikan bahwa pengguna tidak berlangganan pesan yang ditujukan untuk orang lain. Yang ketiga memastikan bahwa pengguna diizinkan untuk berlangganan bidang apa pun, dengan memeriksa atribut DynamoDB yang isAuthorizedForSubscriptions disimpan sebagai. BOOL

Untuk mengujinya, Anda dapat masuk ke AWS AppSync konsol menggunakan kumpulan pengguna Amazon Cognito dan pengguna bernama “Nadia”, lalu jalankan langganan GraphQL berikut:

subscription AuthorizedSubscription { newMessage(toUser: "Nadia") { id toUser fromUser content } }

Jika di tabel Izin ada catatan untuk atribut username kunci Nadia dengan disetel isAuthorizedForSubscriptions ketrue, Anda akan melihat respons yang berhasil. Jika Anda mencoba yang berbeda username dalam ku newMessage() eri di atas, kesalahan akan dikembalikan.