Я пишу REST API с C#/.NET 8 и минимальным API, который вызывается из веб-интерфейса. Я получаю JWTSecurityToken и список идентификаторов групп в полезных данных JWTSecurityToken. Мне нужны названия групп. Кажется, мне нужно запросить Graph, чтобы получить эти имена.
В центре администрирования Entra моему приложению установлено разрешение API «Group.Read.All», предоставленное администратором. И JWTSecurityToken содержит это в «scp» полезных данных.
Я пытаюсь создать GraphServiceClient таким образом (возможно, есть лучший способ, я пробовал разные другие - этот, по крайней мере, дает GraphServiceClient):
var clientSecretCredential = new ClientSecretCredential(tenantId, clientId, clientSecret);
var graphClient = new GraphServiceClient(clientSecretCredential, scopes);
// tenantId, clientId, clientSecret and scopes read from appsettings.json or secrets.json.
Затем я пытаюсь получить имя группы:
string groupId = "<just for test hard coded one existing group id from the token>";
var singleGroup = await graphClient.Groups[groupId].GetAsync();
Console.WriteLine(singleGroup.DisplayName);
Это приводит к появлению разных сообщений об ошибках в зависимости от значения областей, которые я использую в новом GraphServiceClient(clientSecretCredential,scopes):
Для областей применения
scopes = ["Group.Read.All"];
scopes = [$"api://{clientId}/Group.Read.All"];
я получаю Azure.Identity.AuthenticationFailedException: проверка подлинности ClientSecretCredential не удалась: AADTS1002012: Указанное значение области недопустимо. Потоки учетных данных клиента должны иметь значение области с суффиксом /.default к идентификатору ресурса (URI идентификатора приложения).
Для областей (имеющих варианты .default)
scopes = ["Group.Read.All", "https://graph.microsoft.com/.default"];
scopes = ["Group.Read.All", "graph.microsoft.com/.default"];
scopes = ["Group.Read.All", ".default"];
scopes = ["graph.microsoft.com/.default"];
я получаю Azure.Identity.AuthenticationFailedException: проверка подлинности ClientSecretCredential не удалась: AADTS70011: предоставленный запрос должен включать входной параметр «область». Указанное значение входного параметра "область" недопустимо. Область действия недействительна.
Для областей применения
scopes = [".default"];
scopes = ["https://graph.microsoft.com/.default"];
я получаю Microsoft.Graph.Models.ODataErrors.ODataError: недостаточно прав для завершения операции.
Использую ли я правильный пакет Graph? В примерах используется Microsoft.Graph или Microsoft.Identity.Web.MicrosoftGraph, какой из них правильный?
Использую ли я правильную процедуру запуска? У меня есть:
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"))
.EnableTokenAcquisitionToCallDownstreamApi()
.AddInMemoryTokenCaches();
Большинство примеров имеют
.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
и позже
.AddMicrosoftGraph(builder.Configuration.GetSection("MicrosoftGraph"))
which leads to a compiler error when I try to add it to my code.
Пожалуйста, может кто-нибудь указать мне на рабочий пример? Все примеры, которые я нашел, похоже, предназначены для более старых версий Graph и несовместимы с .NET 8.
Обновлено: Разрешения API, по запросу:
@Sridevi: добавил скриншот
Обратите внимание, что поток учетных данных клиента не будет работать с разрешениями делегированного типа. Вам необходимо предоставить Group.Read.All
разрешение типа Приложение для работы с потоком учетных данных клиента.
Вы предоставили Delegated
разрешение API, и теперь у вас есть проект API, чтобы мы могли от имени Azure AD выполнить аутентификацию, а затем вызвать Graph API. Я тестирую минимальный проект API (создал новый минимальный API, но выбираю тип аутентификации в качестве платформы Microsoft Identity с использованием шаблона VS), и вот мой код.
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Graph;
using Microsoft.Identity.Abstractions;
using Microsoft.Identity.Web;
using Microsoft.Identity.Web.Resource;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"))
.EnableTokenAcquisitionToCallDownstreamApi()
.AddMicrosoftGraph()
.AddInMemoryTokenCaches();
builder.Services.AddAuthorization();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
var scope = app.Services.CreateScope();
var _graphClient = scope.ServiceProvider.GetRequiredService<GraphServiceClient>();
var scopeRequiredByApi = app.Configuration["AzureAd:Scopes"] ?? "";
var summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
app.MapGet("/weatherforecast", async (HttpContext httpContext) =>
{
var groupId = "group_object_id";
var singleGroup = await _graphClient.Groups[groupId].GetAsync();
httpContext.VerifyUserHasAnyAcceptedScope(scopeRequiredByApi);
var forecast = Enumerable.Range(1, 5).Select(index =>
new WeatherForecast
(
DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
Random.Shared.Next(-20, 55),
summaries[Random.Shared.Next(summaries.Length)]
))
.ToArray();
return forecast;
})
.WithName("GetWeatherForecast")
.WithOpenApi()
.RequireAuthorization();
app.Run();
internal record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary)
{
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}
Вот мои пакеты nuget.
<ItemGroup>
<PackageReference Include = "Microsoft.AspNetCore.Authentication.JwtBearer" Version = "8.0.8" NoWarn = "NU1605" />
<PackageReference Include = "Microsoft.AspNetCore.Authentication.OpenIdConnect" Version = "8.0.8" NoWarn = "NU1605" />
<PackageReference Include = "Microsoft.AspNetCore.OpenApi" Version = "8.0.8" />
<PackageReference Include = "Microsoft.Identity.Web" Version = "3.1.0" />
<PackageReference Include = "Microsoft.Identity.Web.DownstreamApi" Version = "3.1.0" />
<PackageReference Include = "Swashbuckle.AspNetCore" Version = "6.7.3" />
<PackageReference Include = "Microsoft.Graph" Version = "5.56.0" />
<PackageReference Include = "Microsoft.Graph.Core" Version = "3.1.18" />
<PackageReference Include = "Microsoft.Identity.Web.GraphServiceClient" Version = "3.1.0" />
</ItemGroup>
И нам нужно добавить конфигурации в appsettings.json.
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"Domain": "tenant_id",
"TenantId": "tenant_id",
"ClientId": "client_id",
"ClientSecret": "client_secret",
"Scopes": "Tiny.Greet",//the api permission name of the api I exposed
"CallbackPath": "/signin-oidc"
},
Если у вас есть сомнения по поводу того, как получить пользовательскую область API, следуйте этому руководству, чтобы узнать, как защитить наш API через Azure AD.
Обратите внимание, что поток учетных данных клиента не будет работать с разрешениями типа Delegated
. Правильная область для использования с потоком учетных данных клиента: https://graph.microsoft.com/.default.
Первоначально я тоже получил ту же ошибку, когда пытался получить имя группы, предоставив разрешения Delegated
с потоком учетных данных клиента:
Чтобы устранить ошибку, добавьте Group.Read.All
разрешение типа приложения и обязательно предоставьте на него согласие администратора при работе с потоком учетных данных клиента, как показано ниже:
Когда я снова запустил код после предоставления разрешения типа Application
, я успешно получил ответ с именем группы, как показано ниже:
using Azure.Identity;
using Microsoft.Graph;
class Program
{
static async Task Main(string[] args)
{
var tenantId = configuration["AzureAd:TenantId"];
var clientId = configuration["AzureAd:ClientId"];
var clientSecret = configuration["AzureAd:ClientSecret"];
var clientSecretCredential = new ClientSecretCredential(tenantId, clientId, clientSecret);
var graphClient = new GraphServiceClient(clientSecretCredential, new[] { "https://graph.microsoft.com/.default" });
string groupId = "groupId";
try
{
var group = await graphClient.Groups[groupId].GetAsync();
Console.WriteLine($"Group Display Name: {group.DisplayName}");
}
catch (ServiceException ex)
{
Console.WriteLine($"Error retrieving group info: {ex.Message}");
}
catch (Exception ex)
{
Console.WriteLine($"An error occurred: {ex.Message}");
}
}
}
Ответ:
Огромное спасибо, это спасло жизнь!
Не могли бы вы отредактировать свой вопрос и добавить изображение разрешений API с портала?