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

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 Anda API dan pengantar diberikan pada mekanisme Otorisasi Berbutir Halus untuk memahami konsep dan alur. Karena AWS AppSync memungkinkan Anda untuk melakukan operasi penuh logika pada data melalui penggunaan template Pemetaan GraphQL Resolver, Anda dapat melindungi data saat membaca atau menulis dengan cara yang sangat fleksibel menggunakan kombinasi identitas pengguna, persyaratan, dan injeksi data.

Jika Anda tidak terbiasa dengan mengedit AWS AppSync Resolvers, tinjau panduan pemrograman.

Gambaran Umum

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

AWS AppSync menggunakan sumber daya di akun Anda sendiri dan memasukkan informasi identitas (pengguna/peran) ke dalam permintaan dan respons GraphQL sebagai objek konteks, yang dapat Anda gunakan dalam 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 bernama tertentu yang dapat membaca/menulis ke 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 pengguna/grup. Misalnya mungkin ada atribut Pembaca dan Penulis.

Dari tingkat tinggi, artinya ini adalah jika Anda membaca item individual dari sumber data, Anda melakukan #if () ... #end pernyataan bersyarat di templat 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 templat permintaan dan respons, Anda juga dapat menggunakan header khusus dari klien untuk melakukan pemeriksaan validasi.

Membaca data

Seperti 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 table with ID, Data, PeopleCanAccess, GroupsCanAccess, and Owner columns.

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

Dalam ikhtisar template pemetaan resolver diagram menunjukkan bagaimana template respons tidak hanya berisi objek konteks tetapi juga hasil dari sumber data. Untuk kueri GraphQL dari masing-masing item, 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 query GraphQL mengembalikan daftar, menggunakan Scan atau Query, akan lebih baik 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. Operasi Scan/Query - pemeriksaan otorisasi adalah pernyataan. "filter":{"expression":...} Pemeriksaan umum adalah kesetaraan (attribute = :input) atau memeriksa apakah nilai ada dalam daftar (contains(attribute, :input)).

Dalam #2 attribute dalam kedua pernyataan mewakili nama kolom catatan dalam tabel, seperti Owner dalam contoh di atas. Anda dapat alias ini dengan # tanda dan penggunaan "expressionNames":{...} tetapi itu tidak wajib. :inputIni adalah referensi ke nilai yang Anda bandingkan dengan atribut database, yang akan Anda definisikan"expressionValues":{...}. Anda akan melihat contoh-contoh 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 yang perlu disebutkan di sini yang akan digunakan kembali di bagian yang tersisa. Pertama, cek menggunakan $context.identity.username yang akan menjadi nama pendaftaran pengguna yang ramah jika kumpulan pengguna Amazon Cognito digunakan dan akan menjadi identitas pengguna IAM jika digunakan (termasuk Identitas Federasi Amazon Cognito). Ada nilai lain yang harus disimpan bagi 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 kondisional else yang merespons sepenuhnya opsional tetapi direkomendasikan sebagai praktik terbaik saat mendesain GraphQL Anda. $util.unauthorized() API

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 terhadap $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 pengguna untuk melihat. Misalkan Owner bidang memiliki IdentityId Amazon Cognito kali ini ditetapkan pada catatan, Anda kemudian dapat menggunakan template 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 mengizinkan 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 tercantum dalam set untuk. 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) } } }

Kasus penggunaan: grup dapat membaca

Mirip dengan kasus penggunaan terakhir, mungkin hanya orang dalam satu atau lebih grup yang memiliki hak untuk membaca item tertentu dalam database. Penggunaan "expression": "contains()" operasi serupa namun ini adalah logis-atau dari semua grup yang mungkin menjadi bagian dari 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 dikontrol pada template pemetaan permintaan. Dalam kasus sumber data DynamoDB, kuncinya adalah menggunakan yang "condition":{"expression"...}" sesuai yang melakukan validasi terhadap metadata otorisasi dalam tabel tersebut. 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: banyak pemilik

Menggunakan diagram tabel contoh dari sebelumnya, misalkan PeopleCanAccess daftarnya

{ "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 bersyarat Anda juga dapat memilih untuk menandai data sebagai pribadi, publik atau pemeriksaan Boolean lainnya. Ini kemudian dapat digabungkan sebagai bagian dari filter otorisasi di dalam template respons. Menggunakan pemeriksaan ini adalah cara yang bagus untuk menyembunyikan data sementara atau menghapusnya dari tampilan tanpa mencoba mengontrol keanggotaan grup.

Misalnya Anda menambahkan atribut pada setiap item dalam tabel DynamoDB Anda public dipanggil dengan nilai atau. yes no Template respons berikut dapat digunakan pada GetItem panggilan untuk hanya menampilkan data jika pengguna berada dalam grup yang memiliki akses AND 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 merekam 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.

Data waktu nyata

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 melakukan kueri data dari sumber data dan melakukan logika bersyarat baik dalam templat 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 banyak orang. Ini dapat dikelompokkan ke dalam “kamar”, yang bersifat pribadi atau publik. Dengan demikian, Anda hanya ingin memberi wewenang kepada pengguna untuk berlangganan percakapan (yang bisa berupa satu lawan satu atau di antara grup) yang telah diberikan aksesnya. Untuk tujuan demonstrasi, contoh di bawah ini menunjukkan kasus penggunaan sederhana dari satu pengguna mengirim pesan pribadi ke yang lain. Pengaturan memiliki dua tabel Amazon DynamoDB:

  • Tabel pesan: (kunci utama)toUser, (kunci sortir) 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 DynamoDB resolver. Sebagai gantinya, kami akan fokus pada alur otorisasi berlangganan 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 passthrough 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 hasilnya dikembalikan. Yang kedua memastikan bahwa pengguna tidak berlangganan pesan yang dimaksudkan untuk orang lain. Yang ketiga memastikan bahwa pengguna diizinkan untuk berlangganan bidang apa pun, dengan memeriksa atribut DynamoDB disimpan sebagai fileisAuthorizedForSubscriptions. BOOL

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

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

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