Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
O Microsoft Graph permite que as aplicações subscrevam e recebam notificações sobre alterações aos recursos. Este artigo explica como configurar notificações avançadas, que incluem dados de recursos diretamente no payload de notificação.
As notificações avançadas removem a necessidade de chamadas adicionais à API para obter recursos atualizados, tornando-a mais rápida e fácil de executar a lógica de negócio.
Recursos com suporte
Estão disponíveis notificações avançadas para os seguintes recursos.
Observação
As notificações avançadas para subscrições para pontos finais marcadas com um asterisco (*) só estão disponíveis no /beta ponto final.
| Recurso | Caminhos de recursos suportados | Limitações |
|---|---|---|
| Copilot aiInteraction | Interações de IA copilot das quais um determinado utilizador faz parte: copilot/users/{userId}/interactionHistory/getAllEnterpriseInteractions Interações de IA do Copilot numa organização: copilot/interactionHistory/getAllEnterpriseInteractions |
Cotas máximas de assinaturas: |
| Evento do Outlook | Alterações a todos os eventos na caixa de correio de um utilizador: /users/{id}/events |
$select É necessário devolver apenas um subconjunto de propriedades na notificação avançada. Para obter mais informações, veja Alterar notificações para recursos do Outlook. |
| Mensagem do Outlook | Alterações a todas as mensagens na caixa de correio de um utilizador: /users/{id}/messages Alterações às mensagens na caixa de entrada de um utilizador: /users/{id}/mailFolders/{id}/messages |
$select É necessário devolver apenas um subconjunto de propriedades na notificação avançada. Para obter mais informações, veja Alterar notificações para recursos do Outlook. |
| Contato pessoal do Outlook | Alterações a todos os contactos pessoais na caixa de correio de um utilizador: /users/{id}/contacts Alterações a todos os contactos pessoais no contacto de um utilizadorPasta: /users/{id}/contactFolders/{id}/contacts |
$select É necessário devolver apenas um subconjunto de propriedades na notificação avançada. Para obter mais informações, veja Alterar notificações para recursos do Outlook. |
| Chamada do TeamsRegisto | Todas as gravações numa organização: communications/onlineMeetings/getAllRecordings Todas as gravações para uma reunião específica: communications/onlineMeetings/{onlineMeetingId}/recordings Uma gravação de chamada que fica disponível numa reunião organizada por um utilizador específico: users/{id}/onlineMeetings/getAllRecordings Uma gravação de chamada que fica disponível numa reunião onde está instalada uma determinada aplicação do Teams: appCatalogs/teamsApps/{id}/installedToOnlineMeetings/getAllRecordings * |
Cotas máximas de assinaturas: |
| Chamada do TeamsTranscript | Todas as transcrições numa organização: communications/onlineMeetings/getAllTranscripts Todas as transcrições de uma reunião específica: communications/onlineMeetings/{onlineMeetingId}/transcripts Uma transcrição de chamada que fica disponível numa reunião organizada por um utilizador específico: users/{id}/onlineMeetings/getAllTranscripts Uma transcrição de chamadas que fica disponível numa reunião onde está instalada uma determinada aplicação do Teams: appCatalogs/teamsApps/{id}/installedToOnlineMeetings/getAllTrancripts * |
Cotas máximas de assinaturas: |
| Canal do Teams | Alterações aos canais em todas as equipas: /teams/getAllChannelsAlterações ao canal numa equipa específica: /teams/{id}/channels |
- |
| Chat do Teams | Alterações a qualquer conversa no inquilino: /chatsAlterações a um chat específico: /chats/{id} |
- |
| Teams chatMessage | Alterações às mensagens de chat em todos os canais em todas as equipas: /teams/getAllMessagesAlterações às mensagens de chat num canal específico: /teams/{id}/channels/{id}/messagesAlterações às mensagens de chat em todas as conversas: /chats/getAllMessagesAlterações às mensagens de chat num chat específico: /chats/{id}/messagesAs alterações às mensagens de chat em todas as conversas dos utilizadores específicos fazem parte: /users/{id}/chats/getAllMessages |
Não suporta a utilização $select para devolver apenas as propriedades selecionadas. A notificação avançada consiste em todas as propriedades da instância alterada. |
| conversationMember do Teams | Alterações à associação numa equipa específica: /teams/{id}/membersAlterações à associação em todas as equipas no inquilino: /teams/getAllMembersAlterações à associação em todos os canais numa equipa específica: /teams/{id}/channels/getAllMembersAlterações à associação para todos os canais em todo o inquilino: /teams/getAllChannels/getAllMembersAlterações à associação num chat específico: /chats/{id}/members Alterações à associação para todas as conversas do Teams: /chats/getAllMembers |
Não suporta a utilização $select para devolver apenas as propriedades selecionadas. A notificação avançada consiste em todas as propriedades da instância alterada. |
| Teams onlineMeeting * | Alterações a uma reunião online: /communications/onlineMeetings(joinWebUrl='{encodedJoinWebUrl}')/meetingCallEvents * |
Não suporta a utilização $select para devolver apenas as propriedades selecionadas. A notificação avançada consiste em todas as propriedades da instância alterada. Uma subscrição permitida por aplicação por reunião online. Para obter mais informações, consulte Obter notificações de alteração para atualizações de eventos de chamada de reunião do Microsoft Teams. |
| Teams presença | Alterações à presença de um único utilizador: /communications/presences/{id} Alterações à presença de vários utilizadores: /communications/presences?$filter=id in ({id},{id}...) |
A subscrição para a presença de vários utilizadores está limitada a 650 utilizadores distintos. Não suporta a utilização $select para devolver apenas as propriedades selecionadas. A notificação avançada consiste em todas as propriedades da instância alterada. Uma subscrição permitida por aplicação por utilizador delegado. Para obter mais informações, consulte Obter notificações de alteração para atualizações de presença no Microsoft Teams. |
| Equipe do Teams | Alterações a qualquer equipa no inquilino: /teamsAlterações a uma equipa específica: /teams/{id} |
- |
Dados de recursos na carga de notificação
As notificações avançadas incluem dados de recursos com estes detalhes:
- O ID e o tipo da instância de recurso alterada, encontrados na propriedade resourceData .
- Todos os valores de propriedade da instância de recurso, encriptados conforme especificado na subscrição, encontrados na propriedade encryptedContent .
- Propriedades específicas do recurso, consoante o recurso, ou se solicitado através de um
$selectparâmetro no URL do recurso da subscrição.
Criar uma assinatura
Para configurar notificações avançadas, siga os mesmos passos das notificações de alteração básicas, mas inclua estas propriedades necessárias:
-
includeResourceData: defina esta opção para
truepedir dados de recursos. - encryptionCertificate: forneça a chave pública que o Microsoft Graph utiliza para encriptar os dados de recursos. Saiba mais em Desencriptar dados de recursos a partir de notificações de alteração.
- encryptionCertificateId: forneça um identificador para que o certificado corresponda às notificações com a chave de desencriptação correta.
Valide ambos os pontos de extremidade conforme descrito em Validação de ponto de extremidade de notificação. Se utilizar o mesmo URL para ambos os pontos finais, receberá e deverá responder a dois pedidos de validação.
Exemplo: Pedido de subscrição
Este exemplo cria uma subscrição para mensagens de canal no Microsoft Teams.
POST https://graph.microsoft.com/v1.0/subscriptions
Content-Type: application/json
{
"changeType": "created,updated",
"notificationUrl": "https://webhook.azurewebsites.net/api/resourceNotifications",
"resource": "/teams/{id}/channels/{id}/messages",
"includeResourceData": true,
"encryptionCertificate": "{base64encodedCertificate}",
"encryptionCertificateId": "{customId}",
"expirationDateTime": "2019-09-19T11:00:00.0000000Z",
"clientState": "{secretClientState}"
}
Resposta de assinatura
HTTP/1.1 201 Created
Content-Type: application/json
{
"changeType": "created,updated",
"notificationUrl": "https://webhook.azurewebsites.net/api/resourceNotifications",
"resource": "/teams/{id}/channels/{id}/messages",
"includeResourceData": true,
"encryptionCertificateId": "{customId}",
"expirationDateTime": "2019-09-19T11:00:00.0000000Z",
"clientState": "{secretClientState}"
}
Notificações do ciclo de vida da assinatura
Os eventos podem interromper o fluxo de notificações de alteração numa subscrição. As notificações de ciclo de vida indicam que ações tomar para manter o fluxo ininterruptamente. Ao contrário das notificações de alteração de recursos, as notificações de ciclo de vida focam-se no estado da subscrição.
Para saber mais, veja Reduzir subscrições em falta e alterar notificações.
Validar a autenticidade das notificações
Verifique sempre a autenticidade das notificações de alteração antes de as processar. Isto impede que a sua aplicação acione lógica de negócio incorreta através de notificações falsas de terceiros.
Para notificações básicas, valide-as com o valor clientState , conforme explicado em Processar a notificação de alteração. Para notificações avançadas, execute passos de validação adicionais.
Tokens de validação na notificação de alteração
As notificações avançadas incluem uma propriedade validationTokens , que contém uma matriz de JSON Web Tokens (JWT). Cada token é exclusivo para a aplicação e o par de inquilinos. Uma notificação de alteração pode conter uma combinação de itens para várias aplicações e inquilinos que subscreveram com o mesmo notificationUrl.
Observação
O Microsoft Graph não envia tokens de validação para notificações de alteração fornecidas através de Hubs de Eventos do Azure porque o serviço de subscrição não precisa de validar o notificationUrl para os Hubs de Eventos.
No exemplo a seguir, a notificação de alteração contém dois itens para o mesmo aplicativo e para dois locatários diferentes, portanto, o conjunto validationTokens contém dois tokens que precisam ser validados.
{
"value": [
{
"subscriptionId": "76619225-ff6b-4489-96ca-4ef547e78b22",
"tenantId": "aaaabbbb-0000-cccc-1111-dddd2222eeee",
"changeType": "created",
...
},
{
"subscriptionId": "5cfe2387-163c-4006-81bb-1b5e1e060afe",
"tenantId": "bbbbcccc-1111-dddd-2222-eeee3333ffff",
"changeType": "created",
...
}
],
"validationTokens": [
"eyJ0eXAiOiJKV1QiLCJhb...",
"cGlkYWNyIjoiMiIsImlkc..."
]
}
O objeto de notificação de alteração está na estrutura do tipo de recurso changeNotificationCollection.
Como validar
Utilize a Biblioteca de Autenticação da Microsoft (MSAL) ou uma biblioteca de terceiros para validar tokens. Siga estas etapas:
Tenha em atenção os seguintes princípios:
- Responda à notificação com um
HTTP 202 Acceptedcódigo de status imediatamente. - Responda antes de validar a notificação de alteração, mesmo que a validação falhe mais tarde. Responda imediatamente após receber a notificação de alteração, quer armazene notificações em filas para processamento posterior ou processe-as de imediato.
- Aceitar e responder a uma notificação de alteração impede repetições de entrega desnecessárias e oculta os resultados de validação de potenciais atacantes. Pode sempre ignorar uma notificação de alteração inválida depois de a receber.
Especificamente, realize a validação em todos os tokens JWT na coleção validationTokens. Se algum token falhar, considere a notificação de alteração suspeita e investigue mais.
Siga estes passos para validar os tokens e as aplicações que os geram:
Confirme que o token não expirou.
Confirme que o plataforma de identidade da Microsoft emitiu o token e que não foi adulterado.
- Obtenha as chaves de assinatura do ponto de extremidade de configuração comum:
https://login.microsoftonline.com/common/.well-known/openid-configuration. A sua aplicação pode colocar esta configuração em cache durante algum tempo. A configuração é atualizada frequentemente à medida que as chaves de assinatura são rodadas diariamente. - Verifique a assinatura do token JWT usando essas chaves.
Não aceite tokens emitidos por nenhuma outra autoridade.
- Obtenha as chaves de assinatura do ponto de extremidade de configuração comum:
Confirme que o token foi emitido para a sua aplicação.
As etapas a seguir fazem parte da lógica de validação padrão nas bibliotecas de token JWT e podem ser tipicamente executadas como uma única chamada de função.
- Valide a “audiência” no token que corresponde à ID do seu aplicativo.
- Se você tiver mais de um aplicativo recebendo notificações de alterações, verifique a existencia de vários IDs.
Confirme que a propriedade do
azptoken corresponde ao valor esperado de , que representa o editor de0bf30f3b-4a52-48df-9a82-234910c4a086notificação de alteração do Microsoft Graph.
Exemplo de Token JWT
O exemplo seguinte mostra as propriedades no token JWT necessário para validação.
{
// aud is your app's id
"aud": "925bff9f-f6e2-4a69-b858-f71ea2b9b6d0",
"iss": "https://login.microsoftonline.com/9f4ebab6-520d-49c0-85cc-7b25c78d4a93/v2.0",
"iat": 1624649764,
"nbf": 1624649764,
"exp": 1624736464,
"aio": "E2ZgYGjnuFglnX7mtjJzwR5lYaWvAA==",
// azp represents the notification publisher and must always be the same value of 0bf30f3b-4a52-48df-9a82-234910c4a086
"azp": "0bf30f3b-4a52-48df-9a82-234910c4a086",
"azpacr": "2",
"oid": "1e7d79fa-7893-4d50-bdde-164260d9c5ba",
"rh": "0.AX0AtrpOnw1SwEmFzHslx41KkzsP8wtSSt9ImoIjSRDEoIZ9AAA.",
"sub": "1e7d79fa-7893-4d50-bdde-164260d9c5ba",
"tid": "9f4ebab6-520d-49c0-85cc-7b25c78d4a93",
"uti": "mIB4QKCeZE6hK71XUHJ3AA",
"ver": "2.0"
}
Exemplo: verificação de tokens de validação
// add Microsoft.IdentityModel.Protocols.OpenIdConnect and System.IdentityModel.Tokens.Jwt nuget packages to your project
public async Task<bool> ValidateToken(string token, string tenantId, IEnumerable<string> appIds)
{
var configurationManager = new ConfigurationManager<OpenIdConnectConfiguration>(
"https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration",
new OpenIdConnectConfigurationRetriever());
var openIdConfig = await configurationManager.GetConfigurationAsync();
var handler = new JwtSecurityTokenHandler();
try
{
handler.ValidateToken(token, new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateIssuerSigningKey = true,
ValidateLifetime = true,
ValidIssuer = $"https://sts.windows.net/{tenantId}/",
ValidAudiences = appIds,
IssuerSigningKeys = openIdConfig.SigningKeys
}, out _);
return true;
}
catch (Exception ex)
{
Trace.TraceError($"{ex.Message}:{ex.StackTrace}");
return false;
}
}
Descriptografar os dados de recursos de notificações de alteração
A propriedade resourceData numa notificação de alteração inclui o ID básico e as informações de tipo de uma instância de recurso. A propriedade encryptedData tem os dados de recursos completos, encriptados pelo Microsoft Graph com a chave pública fornecida na subscrição. A propriedade também contém valores necessários para verificação e descriptografia. Esta encriptação é feita para aumentar a segurança dos dados dos clientes acedidos através de notificações de alteração. Proteja a chave privada para garantir que terceiros não conseguem desencriptar os dados dos clientes, mesmo que intercetem as notificações de alteração originais.
Nesta secção, irá aprender os seguintes conceitos:
Gerenciar as chaves de criptografia
Obtenha um certificado com um par de chaves assimétricas.
Pode utilizar um certificado autoassinado, uma vez que o Microsoft Graph não verifica o emissor do certificado e utiliza a chave pública apenas para encriptação.
Utilize o Azure Key Vault para criar, rodar e gerir certificados de forma segura. Cerifique-se de que as chaves atendem aos seguintes critérios:
- A chave tem de ser do tipo
RSA. - O tamanho da chave tem de estar entre 2048 bits e 4096 bits.
- A chave tem de ser do tipo
Exporte o certificado no formato X.509 codificado com Base64 e inclua apenas a chave pública.
Ao criar uma assinatura:
Forneça o certificado na propriedade encryptionCertificate , utilizando o conteúdo codificado em Base64 no qual o certificado foi exportado.
Forneça seu próprio identificador na propriedade encryptionCertificateId.
Esse identificador permite corresponder seus certificados às notificações de alterações recebidas e recuperar certificados do seu repositório de certificados. O identificador pode ter no máximo 128 caracteres.
Gerencie com segurança a chave privada com para que seu código de processamento de notificação de alteração acesse a chave privada para decriptar os dados do recurso.
Chaves de rotação
Altere periodicamente as chaves assimétricas para minimizar o risco de uma chave privada ficar comprometida. Siga estas etapas para introduzir um novo par de chaves:
Obtenha um novo certificado com um novo par de chaves assimétricas. Use-o para todas as novas assinaturas sendo criadas.
Atualize as assinaturas existentes com a nova chave de certificado.
- Faça desta atualização parte da renovação regular da subscrição.
- Ou enumere todas as assinaturas e forneça a chave. Use a operação PATCH na assinatura e atualize as propriedades encryptionCertificate e encryptionCertificateId.
Tenha em atenção os seguintes princípios:
- O certificado antigo ainda pode ser utilizado para encriptação durante algum tempo. Seu aplicativo deve ter acesso a certificados novos e antigos para poder descriptografar o conteúdo.
- Use a propriedade encryptionCertificateId em cada notificação de alteração para identificar a chave correta a ser utilizada.
- Elimine o certificado antigo apenas quando não vir notificações de alteração recentes a referenciá-lo.
Descriptografar dados de recursos
Para otimizar o desempenho, o Microsoft Graph usa um processo de criptografia de duas etapas:
- Gera uma chave simétrica de utilização única e utiliza-a para encriptar dados de recursos.
- Ele usa a chave pública assimétrica (que você forneceu ao fazer a assinatura) para criptografar a chave simétrica e a incluir em cada notificação de alteração desta assinatura.
Suponha que a chave simétrica é diferente para cada item na notificação de alteração.
Para decriptar os dados de recursos, o seu aplicativo deve executar as etapas inversas, usando as propriedades encryptedContent em cada notificação de alteração:
Identifique o certificado correto com a propriedade encryptionCertificateId .
Inicialize um componente criptográfico RSA com a chave privada. Uma forma fácil de inicializar um componente RSA é utilizar o Método RSACertificateExtensions.GetRSAPrivateKey(X509Certificate2) com uma instância X509Certificate2 , que contém a chave privada descrita em Gerir chaves de encriptação.
Desencripte a chave simétrica na propriedade dataKey de cada item na notificação de alteração com a sua chave privada. Utilize o Preenchimento de Encriptação Assimétrica Ideal (OAEP) como algoritmo de desencriptação.
Utilize a chave simétrica para calcular a assinatura HMAC-SHA256 para o valor nos dados. Compare-o com o valor em dataSignature. Se não corresponderem, suponha que o payload foi adulterado e não o desencripte.
Desencripte a propriedade de dados com a chave simétrica com a Standard de Encriptação Avançada (AES), como os .NET Aes.
Use os seguintes parâmetros de descriptografia para o algoritmo AES:
- Preenchimento: PKCS7.
- Modo de cifra: CBC.
Defina o “vetor de inicialização” copiando os primeiros 16 bytes da chave simétrica usada para descriptografia.
Os dados desencriptados serão uma cadeia JSON que representa o recurso.
Exemplo: Desencriptar dados de recursos
O exemplo JSON seguinte mostra uma notificação de alteração que inclui valores de propriedade encriptadas de uma instância chatMessage numa mensagem de canal. O @odata.id valor especifica a instância.
{
"value": [
{
"subscriptionId": "76222963-cc7b-42d2-882d-8aaa69cb2ba3",
"changeType": "created",
// Other properties typical in a resource change notification
"resource": "teams('d29828b8-c04d-4e2a-b2f6-07da6982f0f0')/channels('19:f127a8c55ad949d1a238464d22f0f99e@thread.skype')/messages('1565045424600')/replies('1565047490246')",
"resourceData": {
"id": "1565293727947",
"@odata.type": "#Microsoft.Graph.ChatMessage",
"@odata.id": "teams('88cbc8fc-164b-44f0-b6a6-b59b4a1559d3')/channels('19:8d9da062ec7647d4bb1976126e788b47@thread.tacv2')/messages('1565293727947')/replies('1565293727947')"
},
"encryptedContent": {
"data": "{encrypted data that produces a full resource}",
"dataSignature": "<HMAC-SHA256 hash>",
"dataKey": "{encrypted symmetric key from Microsoft Graph}",
"encryptionCertificateId": "MySelfSignedCert/DDC9651A-D7BC-4D74-86BC-A8923584B0AB",
"encryptionCertificateThumbprint": "07293748CC064953A3052FB978C735FB89E61C3D"
}
}
],
"validationTokens": [
"eyJ0eXAiOiJKV1QiLCJhbGciOiJSU..."
]
}
Para obter uma descrição completa dos dados enviados quando as notificações de alteração são entregues, veja changeNotificationCollection resource type (Tipo de recurso changeNotificationCollection).
Descriptografar a chave simétrica
A seção contém alguns trechos de código úteis que usam C# e .NET em cada etapa da descriptografia.
// Initialize with the private key that matches the encryptionCertificateId.
X509Certificate2 certificate = <instance of X509Certificate2 matching the encryptionCertificateId property>;
RSA rsa = certificate.GetRSAPrivateKey();
byte[] encryptedSymmetricKey = Convert.FromBase64String(<value from dataKey property>);
// Decrypt using OAEP padding.
byte[] decryptedSymmetricKey = rsa.Decrypt(encryptedSymmetricKey, RSAEncryptionPadding.OaepSHA1);
// Can now use decryptedSymmetricKey with the AES algorithm.
Comparar assinatura de dados usando HMAC-SHA256
byte[] decryptedSymmetricKey = <the aes key decrypted in the previous step>;
byte[] encryptedPayload = <the value from the data property, still encrypted>;
byte[] expectedSignature = <the value from the dataSignature property>;
byte[] actualSignature;
using (HMACSHA256 hmac = new HMACSHA256(decryptedSymmetricKey))
{
actualSignature = hmac.ComputeHash(encryptedPayload);
}
if (actualSignature.SequenceEqual(expectedSignature))
{
// Continue with decryption of the encryptedPayload.
}
else
{
// Do not attempt to decrypt encryptedPayload. Assume notification payload has been tampered with and investigate.
}
Descriptografar o conteúdo dos dados de recursos
Aes aesProvider = Aes.Create();
aesProvider.Key = decryptedSymmetricKey;
aesProvider.Padding = PaddingMode.PKCS7;
aesProvider.Mode = CipherMode.CBC;
// Obtain the initialization vector from the symmetric key itself.
int vectorSize = 16;
byte[] iv = new byte[vectorSize];
Array.Copy(decryptedSymmetricKey, iv, vectorSize);
aesProvider.IV = iv;
byte[] encryptedPayload = Convert.FromBase64String(<value from data property>);
string decryptedResourceData;
// Decrypt the resource data content.
using (var decryptor = aesProvider.CreateDecryptor())
{
using (MemoryStream msDecrypt = new MemoryStream(encryptedPayload))
{
using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
{
using (StreamReader srDecrypt = new StreamReader(csDecrypt))
{
decryptedResourceData = srDecrypt.ReadToEnd();
}
}
}
}
// decryptedResourceData now contains a JSON string that represents the resource.
Conteúdo relacionado
- Configure o tipo de recurso da subscrição.