asp.net-core - Blazor WASM 托管 - 在 API 上授权始终返回 UnAuthorized
问题描述
我有一个使用角色身份验证设置的 blazor wasm 托管解决方案。但是,每当我向我的任何 API 控制器添加 [Authorize] 属性时,我都会得到 401 Unauthorized。我知道用户具有适当的角色,因为 UI 正在显示和隐藏该角色的功能。就像角色没有被传递给API一样。我错过了什么?
服务器 - Starup.cs
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
//Register the Datacontext and Connection String
services.AddDbContext<DataContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddDatabaseDeveloperPageExceptionFilter();
//Sets up the default Asp.net core Identity Screens - Use Identity Scaffolding to override defaults
services.AddDefaultIdentity<ApplicationUser>( options =>
{
options.SignIn.RequireConfirmedAccount = true;
options.Password.RequireDigit = true;
options.Password.RequireLowercase = true;
options.Password.RequireUppercase = true;
options.Password.RequiredUniqueChars = 0;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequiredLength = 8;
options.User.RequireUniqueEmail = true;
})
.AddRoles<IdentityRole>()
.AddEntityFrameworkStores<DataContext>();
//Associates the User to Context with Identity
services.AddIdentityServer()
.AddApiAuthorization<ApplicationUser, DataContext>( options =>
{
options.IdentityResources["openid"].UserClaims.Add(JwtClaimTypes.Role);
options.ApiResources.Single().UserClaims.Add(JwtClaimTypes.Role);
});
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove(JwtClaimTypes.Role);
//Adds authentication handler
services.AddAuthentication().AddIdentityServerJwt();
//Register Repositories for Dependency Injection
services.AddScoped<ICountryRepository, CountryRepository>();
services.AddControllersWithViews();
services.AddRazorPages();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, DataContext dataContext)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseMigrationsEndPoint();
app.UseWebAssemblyDebugging();
}
else
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
//AutoMigrates data
dataContext.Database.Migrate();
app.UseHttpsRedirection();
app.UseBlazorFrameworkFiles();
app.UseStaticFiles();
app.UseSerilogIngestion();
app.UseSerilogRequestLogging();
app.UseRouting();
app.UseIdentityServer();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
endpoints.MapControllers();
endpoints.MapFallbackToFile("index.html");
});
}
}
客户端 - Program.cs
public class Program
{
public static async Task Main(string[] args)
{
//Serilog
var levelSwitch = new LoggingLevelSwitch();
Log.Logger = new LoggerConfiguration()
.MinimumLevel.ControlledBy(levelSwitch)
.Enrich.WithProperty("InstanceId", Guid.NewGuid().ToString("n"))
.WriteTo.BrowserHttp(controlLevelSwitch: levelSwitch)
.CreateLogger();
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.Services.AddHttpClient("XXX.ServerAPI", client => client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress))
.AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();
// Supply HttpClient instances that include access tokens when making requests to the server project
builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>().CreateClient("XXX.ServerAPI"));
builder.Services.AddApiAuthorization()
.AddAccountClaimsPrincipalFactory<RolesClaimsPrincipalFactory>();
var baseAddress = new Uri($"{builder.HostEnvironment.BaseAddress}api/");
void RegisterTypedClient<TClient, TImplementation>(Uri apiBaseUrl)
where TClient : class where TImplementation : class, TClient
{
builder.Services.AddHttpClient<TClient, TImplementation>(client =>
{
client.BaseAddress = apiBaseUrl;
});
}
RegisterTypedClient<ICountryService, CountryService>(baseAddress);
await builder.Build().RunAsync();
}
}
RolesClaimPrincipalFactory.cs
public class RolesClaimsPrincipalFactory : AccountClaimsPrincipalFactory<RemoteUserAccount>
{
public RolesClaimsPrincipalFactory(IAccessTokenProviderAccessor accessor) : base(accessor)
{
}
public async override ValueTask<ClaimsPrincipal> CreateUserAsync(
RemoteUserAccount account,
RemoteAuthenticationUserOptions options)
{
ClaimsPrincipal user = await base.CreateUserAsync(account, options);
if (user.Identity.IsAuthenticated)
{
var identity = (ClaimsIdentity)user.Identity;
Claim[] roleClaims = identity.FindAll(identity.RoleClaimType).ToArray();
if (roleClaims != null && roleClaims.Any())
{
foreach (Claim existingClaim in roleClaims)
{
identity.RemoveClaim(existingClaim);
}
var rolesElem = account.AdditionalProperties[identity.RoleClaimType];
if (rolesElem is JsonElement roles)
{
if (roles.ValueKind == JsonValueKind.Array)
{
foreach (JsonElement role in roles.EnumerateArray())
{
identity.AddClaim(new Claim(options.RoleClaim, role.GetString()));
}
}
else
{
identity.AddClaim(new Claim(options.RoleClaim, roles.GetString()));
}
}
}
}
return user;
}
}
解决方案
您可能会遇到此问题,因为您使用ICountryService
的是具有自己的 http 客户端,该客户端未配置为在传出请求中包含身份验证令牌 - 没有令牌,没有访问权限。
我们可以通过向客户端添加一个令牌来附加令牌AuthorizationMessageHandler
,就像XXX.ServerAPI
配置您的命名客户端 ( ) 一样。
尝试将您键入的客户端帮助方法更改为:
/* Client Program.cs */
void RegisterTypedClient<TClient, TImplementation>(Uri apiBaseUrl)
where TClient : class where TImplementation : class, TClient
{
builder.Services.AddHttpClient<TClient, TImplementation>(
client => client.BaseAddress = apiBaseUrl)
.AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();
}
您可能希望将助手更改为仅将令牌包含到实际需要它们的客户端(如果您也将该助手用于其他客户端)
推荐阅读
- angular - 在 iPhone X 上使用 Ionic 4
- php - 如何使用智能引号显示旧文本。等与HTML
- ruby-on-rails - 如何在 Ruby on Rails 应用程序中实现自定义 css?
- loops - 批处理 - 使用 for 循环替换文本文件中的行,包括空行
- eucalyptus - 我应该使用哪种 Ubuntu 云映像格式?
- azure - 针对 Azure 中的 VM 的自定义指标
- kotlin - 如何在 Kotlin 中声明 JavaScript 类“google.visualization.AreaChart”?
- php - 在 Woocommerce 商店的产品图片下显示可变产品的产品属性值
- c++ - 在什么情况下可能需要动态分配指向函数的指针?
- c++ - 如何使用 WinAPI 函数检查当前进程是否作为 Windows 服务运行?