Eliminates the services.BuildServiceProvider() captive-provider antipattern (ASP0000) inside AddJwtBearer. The new ConfigureJwtBearerFromTokenService resolves JwtTokenService from the real DI container at runtime and stays in lock-step with JwtTokenService.BuildValidationParameters. All 27 Security.Tests stay green, including the F1 integration tests that exercise /auth/token through the real bearer pipeline.
88 lines
3.8 KiB
C#
88 lines
3.8 KiB
C#
using Microsoft.AspNetCore.Authentication.Cookies;
|
|
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
|
using Microsoft.AspNetCore.DataProtection;
|
|
using Microsoft.AspNetCore.Http;
|
|
using Microsoft.Extensions.Configuration;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using Microsoft.Extensions.Options;
|
|
using ZB.MOM.WW.OtOpcUa.Configuration;
|
|
using ZB.MOM.WW.OtOpcUa.Security.Jwt;
|
|
using ZB.MOM.WW.OtOpcUa.Security.Ldap;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.Security;
|
|
|
|
/// <summary>
|
|
/// Resolves <see cref="JwtTokenService"/> from the real DI container at runtime so the bearer
|
|
/// pipeline's <see cref="Microsoft.IdentityModel.Tokens.TokenValidationParameters"/> stay in
|
|
/// lock-step with <see cref="JwtTokenService.BuildValidationParameters"/>. Replaces the prior
|
|
/// <c>services.BuildServiceProvider()</c> antipattern (ASP0000) that built a captive provider
|
|
/// from inside <c>.AddJwtBearer</c>.
|
|
/// </summary>
|
|
internal sealed class ConfigureJwtBearerFromTokenService(JwtTokenService tokenService)
|
|
: IPostConfigureOptions<JwtBearerOptions>
|
|
{
|
|
public void PostConfigure(string? name, JwtBearerOptions options)
|
|
{
|
|
if (name != JwtBearerDefaults.AuthenticationScheme) return;
|
|
options.TokenValidationParameters = tokenService.BuildValidationParameters();
|
|
}
|
|
}
|
|
|
|
public static class ServiceCollectionExtensions
|
|
{
|
|
/// <summary>
|
|
/// Wires cookie+JWT hybrid authentication. Cookies are the primary scheme for browser-facing
|
|
/// Blazor + Razor flows; JWT bearer is layered in for external API consumers (OPC UA client
|
|
/// tools, scripts). DataProtection keys persist to the shared ConfigDb so cookies survive
|
|
/// failover between nodes.
|
|
/// </summary>
|
|
public static IServiceCollection AddOtOpcUaAuth(this IServiceCollection services, IConfiguration configuration)
|
|
{
|
|
services.AddOptions<JwtOptions>().Bind(configuration.GetSection(JwtOptions.SectionName));
|
|
services.AddOptions<OtOpcUaCookieOptions>().Bind(configuration.GetSection(OtOpcUaCookieOptions.SectionName));
|
|
services.AddOptions<LdapOptions>().Bind(configuration.GetSection(LdapOptions.SectionName));
|
|
|
|
services.AddSingleton<JwtTokenService>();
|
|
services.AddScoped<ILdapAuthService, LdapAuthService>();
|
|
|
|
services.AddDataProtection()
|
|
.PersistKeysToDbContext<OtOpcUaConfigDbContext>()
|
|
.SetApplicationName("OtOpcUa");
|
|
|
|
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
|
|
.AddCookie(o =>
|
|
{
|
|
o.Cookie.Name = "OtOpcUa.Auth";
|
|
o.Cookie.HttpOnly = true;
|
|
o.Cookie.SameSite = SameSiteMode.Strict;
|
|
o.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest;
|
|
o.SlidingExpiration = true;
|
|
o.ExpireTimeSpan = TimeSpan.FromMinutes(30);
|
|
o.Events.OnRedirectToLogin = ctx =>
|
|
{
|
|
ctx.Response.StatusCode = StatusCodes.Status401Unauthorized;
|
|
return Task.CompletedTask;
|
|
};
|
|
o.Events.OnRedirectToAccessDenied = ctx =>
|
|
{
|
|
ctx.Response.StatusCode = StatusCodes.Status403Forbidden;
|
|
return Task.CompletedTask;
|
|
};
|
|
})
|
|
.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, _ => { /* parameters set by IPostConfigureOptions below */ });
|
|
|
|
services.AddSingleton<IPostConfigureOptions<JwtBearerOptions>, ConfigureJwtBearerFromTokenService>();
|
|
|
|
services.AddAuthorization(o =>
|
|
{
|
|
o.FallbackPolicy = new Microsoft.AspNetCore.Authorization.AuthorizationPolicyBuilder(
|
|
CookieAuthenticationDefaults.AuthenticationScheme,
|
|
JwtBearerDefaults.AuthenticationScheme)
|
|
.RequireAuthenticatedUser()
|
|
.Build();
|
|
});
|
|
|
|
return services;
|
|
}
|
|
}
|