feat(security): cookie+JWT hybrid auth via AddOtOpcUaAuth
This commit is contained in:
3
src/Server/ZB.MOM.WW.OtOpcUa.Security/AssemblyInfo.cs
Normal file
3
src/Server/ZB.MOM.WW.OtOpcUa.Security/AssemblyInfo.cs
Normal file
@@ -0,0 +1,3 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("ZB.MOM.WW.OtOpcUa.Security.Tests")]
|
||||
11
src/Server/ZB.MOM.WW.OtOpcUa.Security/CookieOptions.cs
Normal file
11
src/Server/ZB.MOM.WW.OtOpcUa.Security/CookieOptions.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
namespace ZB.MOM.WW.OtOpcUa.Security;
|
||||
|
||||
public sealed class OtOpcUaCookieOptions
|
||||
{
|
||||
public const string SectionName = "Security:Cookie";
|
||||
|
||||
public string Name { get; set; } = "OtOpcUa.Auth";
|
||||
|
||||
/// <summary>Idle sliding window, in minutes (default 30).</summary>
|
||||
public int ExpiryMinutes { get; set; } = 30;
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
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;
|
||||
|
||||
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, o =>
|
||||
{
|
||||
using var scope = services.BuildServiceProvider().CreateScope();
|
||||
var jwt = scope.ServiceProvider.GetRequiredService<JwtTokenService>();
|
||||
o.TokenValidationParameters = jwt.BuildValidationParameters();
|
||||
});
|
||||
|
||||
services.AddAuthorization(o =>
|
||||
{
|
||||
o.FallbackPolicy = new Microsoft.AspNetCore.Authorization.AuthorizationPolicyBuilder(
|
||||
CookieAuthenticationDefaults.AuthenticationScheme,
|
||||
JwtBearerDefaults.AuthenticationScheme)
|
||||
.RequireAuthenticatedUser()
|
||||
.Build();
|
||||
});
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user