Las traducciones son generadas a través de traducción automática. En caso de conflicto entre la traducción y la version original de inglés, prevalecerá la version en inglés.
Casos de uso del control de acceso para proteger solicitudes y respuestas
En la sección Seguridad ha aprendido cuáles son los diferentes modos de autorización para proteger la API. Además dicha sección contiene una introducción a los mecanismos de autorización precisos para comprender los conceptos y el flujo. Dado que AWS AppSync le permite llevar a cabo operaciones de lógica completas en los datos mediante el uso de plantillas de mapeo de solucionador de GraphQL, puede proteger los datos de lectura o escritura de una forma muy flexible mediante una combinación de identidad del usuario, condiciones e inyección de datos.
Si no está familiarizado con la edición de solucionadores de AWS AppSync, consulte la guía de programación.
Información general
La concesión de acceso a los datos de un sistema se realiza normalmente a través de una matriz de control de acceso
AWS AppSync utiliza los recursos de su cuenta e integra la información de identidad (usuario/rol) en la solicitud y respuesta de GraphQL en forma de un objeto de contexto que se puede utilizar en el solucionador. Esto significa que los permisos se pueden conceder de forma adecuada en operaciones de lectura o escritura en función de la lógica del solucionador. Si esta lógica se aplica en el nivel de recursos, por ejemplo si únicamente determinados usuarios o grupos designados pueden leer/escribir en una fila de base de datos, entonces es necesario almacenar los “metadatos de autorización”. AWS AppSync no almacena datos, por lo que deberá almacenar estos metadatos de autorización con los recursos para que puedan determinarse los permisos. Normalmente los metadatos de la autorización son un atributo (columna) de una tabla de DynamoDB, por ejemplo un propietario o una lista de usuarios o grupos. Por ejemplo, podrían existir los atributos lectores y escritores.
En general esto significa que al leer un elemento individual de un origen de datos, ejecutará una instrucción condicional #if () ... #end
para la plantilla de respuesta una vez que el solucionador haya leído el origen de datos. Normalmente la comprobación usará valores de usuario o grupo de $context.identity
para realizar comprobaciones de pertenencia con los metadatos de autorización que devuelve una operación de lectura. Cuando haya varios registros, como en el caso de listas obtenidas con operaciones Scan
o Query
aplicadas a una tabla, la comprobación de condición se envía como parte de la operación al origen de datos usando valores de usuario o grupo similares.
Del mismo modo, al escribir datos se aplica una instrucción condicional a la acción (como PutItem
o UpdateItem
) a fin de comprobar si el usuario o el grupo que realiza la mutación tiene permiso para ello. A menudo, la instrucción condicional usará un valor de $context.identity
para compararlo con los metadatos de autorización del recurso. Tanto para las plantillas de solicitud como para las de respuesta, también se pueden usar encabezados personalizados de clientes para realizar comprobaciones de validación.
Lectura de datos
Como se ha indicado anteriormente, los metadatos de autorización para realizar una comprobación se deben almacenar con el recurso o se deben transferir a la solicitud de GraphQL (identidad, encabezado, etc.). Como demostración, supongamos que tiene la tabla de DynamoDB siguiente:

La clave principal es id
y los datos a los que se debe obtener acceso son Data
. El resto de columnas son ejemplos de comprobaciones que puede realizar para la autorización. Owner
es de tipo String
, mientras que PeopleCanAccess
y GroupsCanAccess
son String Sets
, como se describe en Resolver mapping template reference for DynamoD.
El diagrama de Información general sobre plantillas de mapeo de solucionador muestra cómo la plantilla de respuesta no solo contiene el objeto de contexto, sino también los resultados del origen de datos. En las consultas de GraphQL referidas a elementos individuales puede utilizar la plantilla de respuesta para comprobar si el usuario tiene permiso para ver estos resultados o devolver un mensaje de error de autorización. Esto es lo que a veces se denomina “filtro de autorización”. En el caso de las consultas de GraphQL que devuelven listas como resultado de Scan o Query, es más eficaz hacer la comprobación en la plantilla de solicitud y devolver los datos solo si se cumple una condición de autorización. La implementación es entonces la siguiente:
-
GetItem: comprobación de autorización de registros individuales. Se realiza con instrucciones
#if() ... #end
. -
Operaciones Scan o Query: la comprobación de autorización es una instrucción
"filter":{"expression":...}
. Algunas comprobaciones habituales son de igualdad (attribute = :input
) o si un valor se encuentra en una lista (contains(attribute, :input)
).
En el n.º 2, attribute
representa en ambas instrucciones el nombre de columna del registro en una tabla, por ejemplo, Owner
en el ejemplo anterior. Puede aplicar un alias con un signo #
y utilizar, "expressionNames":{...}
, pero no es obligatorio. :input
es una referencia al valor que va a comparar con el atributo de base de datos, que definirá en "expressionValues":{...}
. Verá estos ejemplos a continuación.
Caso de uso: el propietario puede leer
Conforme a la tabla anterior, si quiere que solo se devuelvan datos cuando Owner ==
Nadia
en una operación de lectura individual (GetItem
), la plantilla será similar a la siguiente:
#if($context.result["Owner"] == $context.identity.username) $utils.toJson($context.result) #else $utils.unauthorized() #end
Aquí deben tenerse en cuenta algunos puntos, ya que se volverán a usar en las secciones restantes. En primer lugar, la comprobación utiliza $context.identity.username
, que será el nombre de inicio de sesión fácil de recordar si se usan grupos de usuario de Amazon Cognito y será la identidad de usuario si se utiliza IAM (incluidas las identidades federadas de Amazon Cognito). También deben almacenarse otros valores para el propietario, como el valor único “identidad de Amazon Cognito”, que es útil cuando se federan inicios de sesión desde varias ubicaciones, y no se olvide revisar las opciones disponibles en Resolver Mapping Template Context Reference.
En segundo lugar, la comprobación condicional "else" que responde con $util.unauthorized()
es totalmente opcional, pero se aconseja como práctica recomendada al diseñar una API de GraphQL.
Caso de uso: acceso específico codificado
// 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
Caso de uso: filtro de una lista de resultados
En el ejemplo anterior pudimos realizar directamente una comprobación con $context.result
, ya que se devolvía un único elemento. Sin embargo, algunas operaciones, como Scan, devuelven varios elementos en $context.result.items
, por lo que hay que filtrar la autorización y devolver únicamente los resultados que el usuario tiene permiso para ver. Supongamos que esta vez el campo Owner
tiene el IdentityID de Amazon Cognito establecido en el registro. En este caso, puede utilizar la siguiente plantilla de mapeo de respuestas para filtrar los registros y mostrar solo los que son propiedad del usuario:
#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)
Caso de uso: varias personas pueden leer
Otra opción de autorización frecuente consiste en permitir a un grupo de personas leer datos. En el ejemplo siguiente, "filter":{"expression":...}
solo devuelve valores obtenidos de una tabla si el usuario que ejecuta la consulta GraphQL aparece en el conjunto 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) } } }
Caso de uso: un grupo puede leer
Al igual que en el caso de uso anterior, puede ocurrir que solo las personas de uno o varios grupos tengan derecho a leer determinados elementos de una base de datos. El uso de la operación "expression":
"contains()"
es similar. Sin embargo se trata de una disyuntiva OR lógica de todos los grupos a los que puede pertenecer el usuario, lo que debe tenerse en cuenta en la pertenencia a conjuntos. En este caso hemos creado una instrucción $expression
para cada grupo al que pertenece el usuario y la pasamos al filtro:
#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) } }
Escritura de datos
La escritura de datos en las mutaciones siempre se controla en la plantilla de mapeo de solicitud. En el caso de los orígenes de datos de DynamoDB, la clave radica en utilizar una expresión de condición "condition":{"expression"...}"
adecuada que efectúe validaciones aplicando los metadatos de autorización de la tabla. En Seguridad proporcionamos un ejemplo que puede utilizar para comprobar el campo Author
de una tabla. Los casos de uso de esta sección exploran otras posibilidades.
Caso de uso: varios propietarios
Partiendo del diagrama de tabla del ejemplo anterior, observemos la lista 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) } } }
Caso de uso: el grupo puede crear un registro nuevo
#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) } }
Caso de uso: el grupo puede actualizar un registro existente
#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) } }
Registros públicos y privados
Los filtros condicionales también le permiten marcar datos como privados, públicos o efectuar alguna comprobación booleana. Esto puede combinarse como parte de un filtro de autorización dentro de la plantilla de respuesta. El uso de esta comprobación es una buena manera de ocultar temporalmente datos o eliminarlos de la vista sin tener que controlar la pertenencia al grupo.
Por ejemplo, supongamos que añade un atributo a cada elemento de una tabla de DynamoDB denominada public
con el valor yes
o no
. La plantilla de respuesta siguiente se podría utilizar en una llamada GetItem
para mostrar datos solo si el usuario está en un grupo que tenga acceso Y si los datos están marcados como públicos:
#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
El código anterior también puede utilizar un operador lógico O (||
) para permitir a los usuarios leer si tienen permiso sobre un registro o si este es público:
#if($hasPermission || $context.result.public == 'yes') $utils.toJson($context.result) #else $utils.unauthorized() #end
En general los operadores estándar ==
, !=
, &&
y ||
le resultarán útiles cuando realice comprobaciones de autorización.
Datos en tiempo real
Puede aplicar controles de acceso precisos en las suscripciones a GraphQL en el momento en que un cliente realice una suscripción utilizando las técnicas descritas anteriormente en esta documentación. Asocie un solucionador al campo de suscripción y ya podrá consultar un origen de datos y aplicar lógica condicional en la plantilla de mapeo de solicitud o en la de respuesta. También puede devolver datos adicionales al cliente, como los resultados iniciales de una suscripción, siempre que la estructura de datos coincida con la del tipo devuelto en la suscripción de GraphQL.
Caso de uso: el usuario solo puede suscribirse a conversaciones específicas
Un caso de uso común de datos en tiempo real con suscripciones de GraphQL consiste en crear una aplicación de mensajería o de chat privado. Al crear una aplicación de chat para varios usuarios, pueden producirse conversaciones entre dos personas o entre varias personas. Los usuarios pueden agruparse en “salas” que pueden ser privadas o públicas. En consecuencia, es necesario autorizar a cada usuario a suscribirse únicamente a las conversaciones (con otro usuario o dentro de un grupo) para las que se le haya concedido permiso. Con fines de demostración, el ejemplo siguiente muestra un caso de uso sencillo de un usuario que envía un mensaje privado a otro. La configuración tiene dos tablas de Amazon DynamoDB:
-
Tabla Messages (Mensajes): (clave principal)
toUser
, (clave de ordenación)id
-
Tabla Permissions (Permisos): (clave principal)
username
La tabla Messages almacena los mensajes que se envían a través de una mutación de GraphQL. La suscripción de GraphQL comprueba la tabla Permissions para consultar si existe una autorización en el tiempo de conexión del cliente. En el siguiente ejemplo se presupone que se usa el siguiente esquema de 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 }
Algunas de las operaciones estándar, como createUserPermissions()
, no se tratan a continuación para ilustrar los solucionadores de suscripción, pero son implementaciones estándar de solucionadores de DynamoDB. En su lugar, vamos a centrarnos en los flujos de autorización de suscripción con solucionadores. Para enviar un mensaje de un usuario a otro, asocie un solucionador al campo sendMessage()
y seleccione como origen de datos la tabla Messages con la plantilla de solicitud siguiente:
{ "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), } }
En este ejemplo, usaremos $context.identity.username
. Esto devuelve la información de los usuarios de AWS Identity and Access Management o Amazon Cognito. La plantilla de respuesta simplemente traslada $util.toJson($ctx.result)
. Guarde el resultado y vuelva a la página de esquema. Asocie entonces un solucionador para la suscripción newMessage()
utilizando la tabla Permissions como origen de datos y la siguiente plantilla de mapeo de solicitud:
{ "version": "2018-05-29", "operation": "GetItem", "key": { "username": $util.dynamodb.toDynamoDBJson($ctx.identity.username), }, }
Use entonces la siguiente plantilla de mapeo de respuesta para realizar las comprobaciones de autorización con los datos de la tabla Permissions:
#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
En este caso se hacen tres comprobaciones de autorización. La primera asegura que se devuelva un resultado. La segunda garantiza que el usuario no se suscriba a mensajes destinados a otra persona. La tercera garantiza que el usuario tiene permiso para suscribirse a cualquier campo comprobando el atributo de isAuthorizedForSubscriptions
de DynamoDB almacenado como BOOL
.
Para probar que todo funciona bien, inicie sesión en la consola de AWS AppSync usando grupos de usuarios de Amazon Cognito y un usuario llamado “Nadia” y luego ejecute la siguiente suscripción de GraphQL:
subscription AuthorizedSubscription { newMessage(toUser: "Nadia") { id toUser fromUser content } }
Si en la tabla Permissions hay un registro para el atributo de clave username
Nadia
con isAuthorizedForSubscriptions
establecido en true
, verá una respuesta correcta. Si prueba otro username
en la consulta de newMessage()
anterior, se devolverá un error.