Nota
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
Al configurar la groups
notificación en un token de acceso para la aplicación, el identificador de Microsoft Entra tiene un número máximo de grupos que se pueden devolver en el token de acceso. Cuando se supera el límite, Azure proporciona una notificación de uso por encima del límite de grupo, que es una dirección URL que se puede usar para obtener la lista de grupos completa para el usuario que ha iniciado sesión actualmente. Esta dirección URL usa el punto de conexión de Microsoft Graph. Para obtener más información sobre la groups
notificación, consulte Tokens de acceso en el Plataforma de identidad de Microsoft.
En el caso de los tokens web JSON (JWT), Azure limita el número de grupos que pueden estar presentes en el token a 200. Al solicitar un token de acceso para un recurso que tenga configurada la groups
notificación, si es miembro de más de 200 grupos, obtendrá una notificación de uso por encima del límite de grupos en lugar de obtener los grupos reales.
En este artículo se presenta cómo obtener la lista de grupos de usuarios real de una notificación de uso por encima del límite de grupo mediante un proyecto de ejemplo.
Configuración de la notificación de grupos para la aplicación
Puede configurar la groups
notificación de la aplicación mediante las notificaciones opcionales. Para obtener más información, consulte Configuración y administración de notificaciones opcionales en tokens de identificador, tokens de acceso y tokens SAML.
Si la aplicación es una aplicación de primera entidad (aplicación de Microsoft), no puede configurar la groups
notificación. Solo puede configurarlo con su propio registro de aplicaciones. Si desea configurar la groups
notificación para una aplicación cliente, debe configurarla en un token de identificador.
Descarga del proyecto de ejemplo
Descargue el MSAL.Net_GroupOveragesClaim del proyecto de ejemplo. Muestra cómo obtener la lista de grupos de una notificación de uso por encima del límite.
Antes de ejecutar el proyecto de ejemplo
Configure un registro de aplicación para el proyecto de ejemplo.
El proyecto de ejemplo realizará el flujo de cliente público y el flujo de cliente confidencial, por lo que debe configurar una redirección web (para el flujo de cliente público) y un secreto de cliente (para el flujo de cliente confidencial). Dado que el cliente confidencial debe ir al
users
punto de conexión y buscar los grupos en función del identificador de usuario, que se puede obtener del token de inicio de sesión inicial, la versión de cliente confidencial necesita el permiso de aplicación de Microsoft Graph deGroup.Read.All
. El cliente público simplemente va alme
punto de conexión, ya que hay un contexto de usuario.Configure el proyecto de ejemplo para que funcione con el inquilino mediante la actualización del archivo appsettings.json con los valores adecuados:
{ "Azure": { "ClientId": "{client_id}", "TenantId": "{tenant_id}", "CallbackPath": "http://localhost", "ClientSecret": "{client_secret}", "AppScopes": [ "https://database.windows.net/.default" ], "GraphScopes": [ "User.Read" ] } }
Estas son las explicaciones de la configuración del archivo appsettings.json :
AppScopes
Debe tener un ámbito para el que se haya configurado la
groups
notificación.Normalmente, se trata de una API en el inquilino. Pero en este caso, agregar Azure SQL Database con el permiso user_impersonation funciona para este escenario. El ámbito que ha agregado funciona para esa API. Esto se debe a que la
groups
notificación se ha configurado en esa API.GraphScopes
Agregue los permisos de aplicación Group.Read.All (necesarios para obtener el nombre para mostrar del grupo) y User.Read.All (necesario para obtener la lista de grupos mediante el flujo de credenciales de cliente). Debe proporcionar el consentimiento del administrador para esos permisos. El permiso delegado User.Read ya debería estar ahí. Si no es así, agréguelo.
Una vez configurado el registro de la aplicación para este proyecto de ejemplo, agregue el identificador de cliente (id. de aplicación), el secreto de cliente y el identificador de inquilino en el archivo appsettings.json .
Ejecute scripts de PowerShell en el archivo Create_TestGroup.ps1.txt para crear grupos de prueba (si es necesario).
El proyecto de ejemplo tiene un archivo de texto denominado Create_TestGroup.ps1.txt. Para ejecutar scripts de PowerShell en este archivo, quite la extensión .txt. Antes de ejecutarlo, necesita un identificador de objeto de un usuario para agregarlos a los grupos de prueba. Debe estar en un rol de directorio que pueda crear grupos y agregar usuarios a los grupos. El proyecto de ejemplo creará 255 grupos en el formato de
TEST_0001
,TEST_0002
, etc. Se agregará el identificador de objeto que proporcione para cada grupo. Al final del script, iniciará sesión en Azure, ejecutará el comando para crear los grupos de prueba y, a continuación, cerrará la sesión.Nota:
Hay un método de limpieza de ejemplo que se comenta en la línea 53:
Connect-AzureAD Create-TestGroups -deleteIfExists $false #Delete-TestGroups Disconnect-AzureAD
Obtención de la lista completa de grupos de usuarios mediante la notificación de uso por encima del límite de grupo
Ejecute la aplicación de ejemplo.
Inicie sesión en la aplicación.
La autenticación se produce en un explorador porque la aplicación de ejemplo es una aplicación de consola de .NET.
Después de iniciar sesión, cierre el explorador y se le devolverá a la aplicación de consola.
Una vez que el token de acceso se presenta en la ventana de la consola, copie el token de acceso en el Portapapeles y péguelo en https://jwt.ms para ver el token codificado. Es solo un token de usuario.
Si el usuario es miembro de demasiados grupos, la ventana de la consola mostrará la notificación de uso por encima del límite del grupo original y la nueva notificación de uso por encima del límite del grupo para ese token. La nueva notificación de uso por encima del límite del grupo se usará en la solicitud de cliente HTTP de .NET en lugar de en la solicitud del SDK de .NET de Graph.
Seleccione el tipo de token de acceso que desea obtener para Microsoft Graph. Puede obtener un token de acceso mediante un token de usuario para el usuario que ha iniciado sesión actualmente (flujo de tokens de actualización) o un token de aplicación mediante el flujo de concesión de credenciales de cliente.
Seleccione o
.NET HTTP Request
Graph .NET SDK
para obtener la lista completa de los grupos de usuarios.Los grupos aparecerán en la ventana de la consola:
Sobre el código
método Get_GroupsOverageClaimURL
El proyecto de ejemplo usa MSAL.NET (Microsoft.Identity.Client
) para autenticar a los usuarios y obtener tokens de acceso.
System.Net.Http
se usa para el cliente HTTP y el SDK de Microsoft.Graph se usa para el cliente de grafos. Para analizar el archivo JSON, System.Text.Json
se usa . Para obtener las notificaciones del token, System.IdentityModel.Tokens.Jwt
se usa . El JwtSecurityToken
proveedor se usa para recuperar la notificación de uso por encima del límite del grupo en el token.
Si el token contiene las notificaciones claim_names
y claim_sources
, indica la presencia de una notificación de uso por encima del límite de un grupo dentro del token. En este caso, use el identificador de usuario (oid) y las dos notificaciones para construir la dirección URL en la lista de grupos y generar el valor original en la ventana de la consola. Si alguno de los dos valores de notificación no existe, el try/catch
bloque controlará el error y devolverá un string.empty
valor. Esto indica que no hay ninguna notificación de uso por encima del límite del grupo en el token.
/// <summary>
/// Looks for a group overage claim in an access token and returns the value if found.
/// </summary>
/// <param name="accessToken"></param>
/// <returns></returns>
private static string Get_GroupsOverageClaimURL(string accessToken)
{
JwtSecurityToken token = new JwtSecurityTokenHandler().ReadJwtToken(accessToken);
string claim = string.Empty;
string sources = string.Empty;
string originalUrl = string.Empty;
string newUrl = string.Empty;
try
{
// use the user id in the new graph URL since the old overage link is for Azure AD Graph which is being deprecated.
userId = token.Claims.First(c => c.Type == "oid").Value;
// getting the claim name to properly parse from the claim sources but the next 3 lines of code are not needed,
// just for demonstration purposes only so you can see the original value that was used in the token.
claim = token.Claims.First(c => c.Type == "_claim_names").Value;
sources = token.Claims.First(c => c.Type == "_claim_sources").Value;
originalUrl = sources.Split("{\"" + claim.Split("{\"groups\":\"")[1].Replace("}","").Replace("\"","") + "\":{\"endpoint\":\"")[1].Replace("}","").Replace("\"", "");
// make sure the endpoint is specific for your tenant -- .gov for example for gov tenants, etc.
newUrl = $"https://graph.microsoft.com/v1.0/users/{userId}/memberOf?$orderby=displayName&$count=true";
Console.WriteLine($"Original Overage URL: {originalUrl}");
//Console.WriteLine($"New URL: {newUrl}");
} catch {
// no need to do anything because the claim does not exist
}
return newUrl;
}
Archivo program.cs
En este archivo, hay una configuración de aplicación cliente pública para el inicio de sesión de usuario y obtener tokens de acceso, y una aplicación cliente confidencial para el inicio de sesión de la aplicación y obtener tokens de acceso (el flujo de concesión de credenciales de cliente).
ManualTokenProvider
se usa para que el cliente del servicio Graph pase un token de acceso al servicio en lugar de que Graph lo obtenga.
También hay un archivo appsettings.json y una clase (AzureConfig.cs) para almacenar esa configuración en tiempo de ejecución. La propiedad AzureSettings
estática pública recupera la configuración del archivo de configuración mediante un generador de configuraciones, similar a ASP.NET aplicaciones core. Esta característica se debe agregar, ya que no es nativa de una aplicación de consola.
static AzureConfig _config = null;
public static AzureConfig AzureSettings
{
get
{
// Only load this when the app starts.
// To reload, you will have to set the variable _config to null again before calling this property.
if (_config == null)
{
_config = new AzureConfig();
IConfiguration builder = new ConfigurationBuilder()
.SetBasePath(System.IO.Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.Build();
ConfigurationBinder.Bind(builder.GetSection("Azure"), _config);
}
return _config;
}
}
Proveedor de autenticación
Para el Authentication
proveedor del cliente del servicio Graph, el proyecto de ejemplo usa un proveedor de tokens manual personalizado para establecer el token de acceso para el cliente que ya ha obtenido tokens de acceso mediante MSAL.
using Microsoft.Graph;
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
namespace MSAL.Net_GroupOveragesClaim.Authentication
{
class ManualTokenProvider : IAuthenticationProvider
{
string _accessToken;
public ManualTokenProvider ( string accessToken)
{
_accessToken = accessToken;
}
async Task IAuthenticationProvider.AuthenticateRequestAsync(HttpRequestMessage request)
{
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _accessToken);
request.Headers.Add("ConsistencyLevel", "eventual");
}
}
}
Get_Groups_HTTP_Method
Este método llama al Graph_Request_viaHTTP
método para obtener la lista de grupos y, a continuación, muestra esa lista en la ventana de la consola.
/// <summary>
/// Entry point to make the request to Microsoft graph using the .Net HTTP Client
/// </summary>
/// <param name="graphToken"></param>
/// <returns></returns>
private static async Task Get_Groups_HTTP_Method(string graphToken, string url)
{
List<Group> groupList = new List<Group>();
groupList = await Graph_Request_viaHTTP(graphToken, url);
foreach (Group g in groupList)
{
Console.WriteLine($"Group Id: {g.Id} : Display Name: {g.DisplayName}");
}
}
/// <summary>
/// Calls Microsoft Graph via a HTTP request. Handles paging in the request
/// </summary>
/// <param name="user_access_token"></param>
/// <returns>List of Microsoft Graph Groups</returns>
private static async Task<List<Group>> Graph_Request_viaHTTP(string user_access_token, string url)
{
string json = string.Empty;
//string url = "https://graph.microsoft.com/v1.0/me/memberOf?$orderby=displayName&$count=true";
List<Group> groups = new List<Group>();
// todo: check for the count parameter in the request and add if missing
/*
* refer to this documentation for usage of the http client in .net
* https://docs.microsoft.com/en-us/dotnet/api/system.net.http.httpclient?view=net-5.0
*
*/
// add the bearer token to the authorization header for this request
_httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue( "Bearer", user_access_token);
// adding the consistencylevel header value if there is a $count parameter in the request as this is needed to get a count
// this only needs to be done one time so only add it if it does not exist already. It is case sensitive as well.
// if this value is not added to the header, the results will not sort properly -- if that even matters for your scenario
if(url.Contains("&$count", StringComparison.OrdinalIgnoreCase))
{
if (!_httpClient.DefaultRequestHeaders.Contains("ConsistencyLevel"))
{
_httpClient.DefaultRequestHeaders.Add("ConsistencyLevel", "eventual");
}
}
// while loop to handle paging
while(url != string.Empty)
{
HttpResponseMessage response = await _httpClient.GetAsync(new Uri(url));
url = string.Empty; // clear now -- repopulate if there is a nextlink value.
if (response.IsSuccessStatusCode)
{
json = await response.Content.ReadAsStringAsync();
// Console.WriteLine(json);
using (JsonDocument document = JsonDocument.Parse(json))
{
JsonElement root = document.RootElement;
// check for the nextLink property to see if there is paging that is occuring for our while loop
if (root.TryGetProperty("@odata.nextLink", out JsonElement nextPage))
{
url = nextPage.GetString();
}
JsonElement valueElement = root.GetProperty("value"); // the values
// loop through each value in the value array
foreach (JsonElement value in valueElement.EnumerateArray())
{
if (value.TryGetProperty("@odata.type", out JsonElement objtype))
{
// only getting groups -- roles will show up in this graph query as well.
// If you want those too, then remove this if filter check
if (objtype.GetString() == "#microsoft.graph.group")
{
Group g = new Group();
// specifically get each property you want here and populate it in our new group object
if (value.TryGetProperty("id", out JsonElement id)) { g.Id = id.GetString(); }
if (value.TryGetProperty("displayName", out JsonElement displayName)) { g.DisplayName = displayName.GetString(); }
groups.Add(g);
}
}
}
}
} else
{
Console.WriteLine($"Error making graph request:\n{response.ToString()}");
}
} // end while loop
return groups;
}
Get_Groups_GraphSDK_Method
Del mismo modo, el SDK de Graph tiene un método de entrada, Get_Groups_GraphSDK_Method
. Este método llama Get_GroupList_GraphSDK
a para obtener la lista de grupos y, a continuación, lo muestra en la ventana de la consola.
/// <summary>
/// Entry point to make the request to Microsoft Graph using the Graph SDK and outputs the list to the console.
/// </summary>
/// <param name="graphToken"></param>
/// <returns></returns>
private static async Task Get_Groups_GraphSDK_Method(string graphToken, bool me_endpoint)
{
List<Group> groupList = new List<Group>();
groupList = await Get_GroupList_GraphSDK(graphToken, me_endpoint);
foreach (Group g in groupList)
{
Console.WriteLine($"Group Id: {g.Id} : Display Name: {g.DisplayName}");
}
}
método Get_GroupList_GraphSDK
Este método determina si se debe usar el me
punto de conexión o el users
punto de conexión para obtener la lista de grupos. Si usa el flujo de concesión de credenciales de cliente para obtener el token de acceso de Microsoft Graph, use el me
punto de conexión. Si no es así (por ejemplo, se usa un flujo delegado para el token de acceso), use el users
punto de conexión. Independientemente del método usado, el código controla la paginación porque, de forma predeterminada, solo se devuelven 100 registros por página. La paginación se determina a través del @odata.nextLink
valor . Si hay un valor para esa propiedad, se llama a la dirección URL completa para la siguiente página de datos. Para obtener más información sobre la paginación, consulte Paginación de datos de Microsoft Graph en la aplicación.
/// <summary>
/// Calls the Me.MemberOf endpoint in Microsoft Graph and handles paging
/// </summary>
/// <param name="graphToken"></param>
/// <returns>List of Microsoft Graph Groups</returns>
private static async Task<List<Group>> Get_GroupList_GraphSDK(string graphToken, bool use_me_endpoint)
{
GraphServiceClient client;
Authentication.ManualTokenProvider authProvider = new Authentication.ManualTokenProvider(graphToken);
client = new GraphServiceClient(authProvider);
IUserMemberOfCollectionWithReferencesPage membershipPage = null;
HeaderOption option = new HeaderOption("ConsistencyLevel","eventual");
if (use_me_endpoint)
{
if (!client.Me.MemberOf.Request().Headers.Contains(option))
{
client.Me.MemberOf.Request().Headers.Add(option);
}
membershipPage = await client.Me.MemberOf
.Request()
.OrderBy("displayName&$count=true") // todo: find the right way to add the generic query string value for count
.GetAsync();
} else
{
if (!client.Users[userId].MemberOf.Request().Headers.Contains(option))
{
client.Users[userId].MemberOf.Request().Headers.Add(option);
}
membershipPage = await client.Users[userId].MemberOf
.Request()
.OrderBy("displayName&$count=true")
.GetAsync();
}
List<Group> allItems = new List<Group>();
if(membershipPage != null)
{
foreach(DirectoryObject o in membershipPage)
{
if(o is Group)
{
allItems.Add((Group)o);
}
}
while (membershipPage.AdditionalData.ContainsKey("@odata.nextLink") && membershipPage.AdditionalData["@odata.nextLink"].ToString() != string.Empty)
{
membershipPage = await membershipPage.NextPageRequest.GetAsync();
foreach (DirectoryObject o in membershipPage)
{
if (o is Group)
{
allItems.Add(o as Group);
}
}
}
}
return allItems;
}
Ponte en contacto con nosotros para obtener ayuda
Si tiene preguntas o necesita ayuda, cree una solicitud de soporte o busque consejo en la comunidad de Azure. También puede enviar comentarios sobre el producto con los comentarios de la comunidad de Azure.