在 Blazor 组件中,我想调用目前托管在同一应用程序中的 API。在我添加身份验证和授权之前,这一直有效。从我的浏览器调用 API 工作正常。当我尝试调用相同的 API 时,返回的响应内容是 Microsoft OIDC 登录的 HTML。
当我尝试让用户访问令牌作为不记名令牌传递时,我收到一条错误消息
System.InvalidOperationException: IDW10503: Cannot determine the cloud Instance. The provided authentication scheme was 'MicrosoftOidc'. Microsoft.Identity.Web inferred 'MicrosoftOidc' as the authentication scheme. Available authentication schemes are 'MicrosoftOidc,Bearer,Cookies,OpenIdConnect'. See https://aka.ms/id-web/authSchemes.
所以它说提供的身份验证方案是MicrosoftOidc,被推断为MicrosoftOidc,并且可用的方案包括Microsoft Oidc。我的用户登录并获得分配的角色。我在这一行收到此错误
var token = await tokenAcquisition.GetAccessTokenForUserAsync(new[] { "Spec.Read" }, authenticationScheme: "MicrosoftOidc");
这是我在程序中设置身份验证的方法。cs
builder.Services.AddAuthentication("MicrosoftOidc")
.AddOpenIdConnect("MicrosoftOidc", oidcOptions =>
{
oidcOptions.Scope.Add(OpenIdConnectScope.OfflineAccess);
oidcOptions.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
oidcOptions.SaveTokens = true;
oidcOptions.Authority = builder.Configuration["AzureAd:Instance"];
oidcOptions.ClientId = builder.Configuration["AzureAd:ClientId"];
oidcOptions.ClientSecret = builder.Configuration["AzureAd:ClientSecret"];
oidcOptions.ResponseType = OpenIdConnectResponseType.Code;
oidcOptions.MapInboundClaims = false;
oidcOptions.TokenValidationParameters.NameClaimType = JwtRegisteredClaimNames.Name;
oidcOptions.TokenValidationParameters.RoleClaimType = "roles";
oidcOptions.TokenValidationParameters.IssuerValidator = AadIssuerValidator.GetAadIssuerValidator(
oidcOptions.Authority,
oidcOptions.Backchannel).Validate;
}).AddJwtBearer()
.AddMicrosoftIdentityWebApp(builder.Configuration)
.EnableTokenAcquisitionToCallDownstreamApi(builder.Configuration.GetValue<string[]>("DownstreamApi:Scopes"))
.AddInMemoryTokenCaches();
var map = JsonWebTokenHandler.DefaultInboundClaimTypeMap;
builder.Services.AddAuthorization();
builder.Services.AddCascadingAuthenticationState();
builder.Services.AddRazorPages().AddMvcOptions(options=>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
}).AddMicrosoftIdentityUI();
builder.Services.Configure<ForwardedHeadersOptions>(options =>
{
options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto | ForwardedHeaders.XForwardedHost;
options.KnownNetworks.Clear();
options.KnownProxies.Clear();
options.AllowedHosts = builder.Configuration.GetSection("AllowedHosts").Get<List<string>>();
});
builder.Services.AddServerSideBlazor();
builder.Services.AddControllers();
...
var app = builder.Build();
app.UseRewriter(
new RewriteOptions().Add(context =>
{
if (context.HttpContext.Request.Path == "/signout-oidc")
context.HttpContext.Response.Redirect("https://Corrugated.io");
}));
app.UseForwardedHeaders();
app.UseHttpsRedirection();
app.UseDefaultFiles();
app.UseStaticFiles();
app.UseRouting();
app.MapControllers();
app.UseAuthentication();
app.UseAuthorization();
app.MapBlazorHub();
app.MapFallbackToPage("/_Host");
app.Run();
这是相关应用程序设置
{
"AzureAd": {
"Instance": "https://login.microsoftonline.com/common/",
"TenantId": "be664a41-9b40-4c81-b11e-e52879b29fe9",
"ClientId": "f75fae40-5787-48f4-8834-7354c224e530",
"CallbackPath": "/signin-oidc",
"ClientSecret": "super secret"
},
"DownstreamApi": {
"BaseUrl": "https://graph.microsoft.com/v1.0",
"RelativePath": "me",
"Scopes": [
"user.read"
]
}
}
我在这里做错了什么?访问链接的又名站点并不能真正帮助我,因为它以一种有点不同的方式配置身份验证,但差别不大,基本上只是传递默认值。
此外,我可能尝试以错误的方式调用 API。我的其他组件之一正在对我的另一个 API 控制器进行 js 互操作调用,并且经过了正确的身份验证。我应该这样做吗?我认为传递不记名令牌会更正确。特别是如果我稍后要尝试突破 API。
首先,我不太确定
my API which is hosted in the same application
,因为我们可以看到 builder.Services.AddServerSideBlazor();
中有 Program.cs
,所以这是一个带有 .net 8 (.net 8 使用 blazor web 应用程序来替换 blazor 服务器,所以恐怕您正在将旧的 blazor 服务器应用程序迁移到 .net 8,但不喜欢使用 blazor Web 应用程序功能,但这对于此问题并不重要)。因此,我们现在有一个具有 UI 的应用程序,并且 UI 在服务器端呈现,并且我们在同一项目中公开了 API。现在我们也在项目中调用API。这让我很困惑。
让我们回到您的代码和错误。首先,我们现在使用 MSAL 库进行 Microsoft 身份验证。因此,我们应在 blazor 服务器应用程序中使用
AddMicrosoftIdentityWebAppAuthentication
或 builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme).AddMicrosoftIdentityWebApp
代替 AddAuthentication("MicrosoftOidc").AddOpenIdConnect()
。这里我们有一些针对不同场景的官方示例。
然后我注意到您的 appsettings.json 中有
"BaseUrl": "https://graph.microsoft.com/v1.0",
,所以我担心您正在尝试在 blazor 服务器应用程序中调用 MS Graph API,那么步骤应该是 1. 使用 MS 帐户登录您的应用程序 - > 2. 通过 tokenAcquisition 生成访问令牌 -> 3. 调用 API url 获取响应或 2. 使用 Graph SDK 调用 Graph API 并获取响应。您收到的错误意味着身份验证出现问题。我将向您展示我在我身边测试的样本。
程序.cs
using BlazorServerGraph5.Data;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Mvc.Authorization;
using Microsoft.Identity.Web;
using Microsoft.Identity.Web.UI;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"))
.EnableTokenAcquisitionToCallDownstreamApi()
.AddMicrosoftGraph(builder.Configuration.GetSection("DownstreamApi"))
.AddInMemoryTokenCaches();
builder.Services.AddControllersWithViews()
.AddMicrosoftIdentityUI();
builder.Services.AddAuthorization(options =>
{
// By default, all incoming requests will be authorized according to the default policy
options.FallbackPolicy = options.DefaultPolicy;
});
builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor()
.AddMicrosoftIdentityConsentHandler();
builder.Services.AddSingleton<WeatherForecastService>();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.MapControllers();
app.MapBlazorHub();
app.MapFallbackToPage("/_Host");
app.Run();
FetchData.razor
@page "/fetchdata"
@using BlazorServerGraph5.Data
@inject WeatherForecastService ForecastService
@using Microsoft.Graph
@using Microsoft.Graph.Models
@inject GraphServiceClient graphServiceClient
<PageTitle>Weather forecast</PageTitle>
<h1>Weather forecast</h1>
<p>This component demonstrates fetching data from a service.</p>
@if (forecasts == null)
{
<p><em>Loading...</em></p>
}
else
{
<table class="table">
<thead>
<tr>
<th>Date</th>
<th>Temp. (C)</th>
<th>Temp. (F)</th>
<th>Summary</th>
</tr>
</thead>
<tbody>
@foreach (var forecast in forecasts)
{
<tr>
<td>@forecast.Date.ToShortDateString()</td>
<td>@forecast.TemperatureC</td>
<td>@forecast.TemperatureF</td>
<td>@forecast.Summary</td>
</tr>
}
</tbody>
</table>
}
@code {
private WeatherForecast[]? forecasts;
protected override async Task OnInitializedAsync()
{
var me = await graphServiceClient.Me.GetAsync();
forecasts = await ForecastService.GetForecastAsync(DateOnly.FromDateTime(DateTime.Now));
}
}
应用程序设置.json
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"Domain": "tenant_id",
"TenantId": "tenant_id",
"ClientId": "client_id",
"CallbackPath": "/signin-oidc",
"ClientSecret": "client_secret"//authentication Graph SDK requires client secret
},
"DownstreamApi": {
"BaseUrl": "https://graph.microsoft.com/v1.0",
"Scopes": "User.ReadWrite.All"
},
nuget 包
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="7.0.13" NoWarn="NU1605" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="7.0.13" NoWarn="NU1605" />
<PackageReference Include="Microsoft.Graph" Version="5.40.0" />
<PackageReference Include="Microsoft.Graph.Core" Version="3.1.6" />
<PackageReference Include="Microsoft.Identity.Web" Version="2.16.1" />
<PackageReference Include="Microsoft.Identity.Web.GraphServiceClient" Version="2.16.1" />
<PackageReference Include="Microsoft.Identity.Web.UI" Version="2.16.1" />
</ItemGroup>
创建登录部分组件
<AuthorizeView>
<Authorized>
Hello, @context.User.Identity?.Name!
<a href="MicrosoftIdentity/Account/SignOut">Log out</a>
</Authorized>
<NotAuthorized>
<a href="MicrosoftIdentity/Account/SignIn">Log in</a>
</NotAuthorized>
</AuthorizeView>
并将其放入MainLayout
<div class="top-row px-4 auth">
<LoginDisplay />
<a href="https://docs.microsoft.com/aspnet/" target="_blank">About</a>
</div>
在 App.razor 中,用
<CascadingAuthenticationState>
包围
<CascadingAuthenticationState>
<Router AppAssembly="@typeof(App).Assembly">
<Found Context="routeData">
<AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
<FocusOnNavigate RouteData="@routeData" Selector="h1" />
</Found>
<NotFound>
<PageTitle>Not found</PageTitle>
<LayoutView Layout="@typeof(MainLayout)">
<p role="alert">Sorry, there's nothing at this address.</p>
</LayoutView>
</NotFound>
</Router>
</CascadingAuthenticationState>