feat(auth): ScadaBridge full canonical claims (ZbClaimTypes role/scope) + ZbCookieDefaults, keep cookie name (Task 1.5)
This commit is contained in:
@@ -4,6 +4,7 @@ using System.Text;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using ZB.MOM.WW.Auth.AspNetCore;
|
||||
|
||||
namespace ZB.MOM.WW.ScadaBridge.Security;
|
||||
|
||||
@@ -12,10 +13,20 @@ public class JwtTokenService
|
||||
private readonly SecurityOptions _options;
|
||||
private readonly ILogger<JwtTokenService> _logger;
|
||||
|
||||
public const string DisplayNameClaimType = "DisplayName";
|
||||
public const string UsernameClaimType = "Username";
|
||||
public const string RoleClaimType = "Role";
|
||||
public const string SiteIdClaimType = "SiteId";
|
||||
// Task 1.5 (full canonical claims): these constants are the single source of
|
||||
// truth that every mint site, every authorization policy, and the token
|
||||
// validation parameters reference. Redefining them as aliases of the shared
|
||||
// ZbClaimTypes vocabulary migrates the whole module centrally:
|
||||
// - Role → the framework URI (ClaimTypes.Role) so [Authorize(Roles=…)],
|
||||
// IsInRole, AND RequireClaim(RoleClaimType,…) all resolve.
|
||||
// - SiteId → ZbClaimTypes.ScopeId ("zb:scopeid") — the canonical scope claim.
|
||||
// - DisplayName/Username → the canonical "zb:" strings.
|
||||
// LastActivity has no canonical equivalent (it is a ScadaBridge-internal
|
||||
// idle-timeout anchor), so it keeps its existing literal.
|
||||
public const string DisplayNameClaimType = ZbClaimTypes.DisplayName;
|
||||
public const string UsernameClaimType = ZbClaimTypes.Username;
|
||||
public const string RoleClaimType = ZbClaimTypes.Role;
|
||||
public const string SiteIdClaimType = ZbClaimTypes.ScopeId;
|
||||
public const string LastActivityClaimType = "LastActivity";
|
||||
|
||||
/// <summary>
|
||||
@@ -100,7 +111,19 @@ public class JwtTokenService
|
||||
expires: DateTime.UtcNow.AddMinutes(_options.JwtExpiryMinutes),
|
||||
signingCredentials: credentials);
|
||||
|
||||
return new JwtSecurityTokenHandler().WriteToken(token);
|
||||
// MapOutboundClaims=false: write the claim TYPE strings into the JWT
|
||||
// verbatim (e.g. the ClaimTypes.Role URI for RoleClaimType) rather than
|
||||
// letting JwtSecurityTokenHandler.DefaultOutboundClaimTypeMap rewrite the
|
||||
// framework URI to a short JWT name ("role"). Paired with
|
||||
// MapInboundClaims=false on validation (see ValidateToken), this makes the
|
||||
// claim type the policy checks — RequireClaim(RoleClaimType, …) — byte-for-byte
|
||||
// the same string both in the token and after it is read back, with no
|
||||
// mapping round-trip surprises. The "zb:" scope/display/username claims are
|
||||
// not in any map and were unaffected either way; pinning both directions
|
||||
// makes the role/site migration to canonical types deterministic.
|
||||
var handler = new JwtSecurityTokenHandler { MapInboundClaims = false };
|
||||
handler.OutboundClaimTypeMap.Clear();
|
||||
return handler.WriteToken(token);
|
||||
}
|
||||
|
||||
/// <summary>Validates a JWT string and returns the decoded <see cref="ClaimsPrincipal"/>, or null if the token is invalid or expired.</summary>
|
||||
@@ -118,12 +141,26 @@ public class JwtTokenService
|
||||
ValidateLifetime = true,
|
||||
ValidateIssuerSigningKey = true,
|
||||
IssuerSigningKey = key,
|
||||
ClockSkew = TimeSpan.Zero
|
||||
ClockSkew = TimeSpan.Zero,
|
||||
|
||||
// The token was minted carrying the canonical claim TYPE strings
|
||||
// verbatim (RoleClaimType = ClaimTypes.Role URI, SiteIdClaimType =
|
||||
// ZbClaimTypes.ScopeId, etc.). Pin the same role/name claim types here
|
||||
// so the validated principal's Identity.Name and IsInRole resolve, and
|
||||
// so RequireClaim(RoleClaimType, …) sees exactly the type that is in the
|
||||
// token.
|
||||
RoleClaimType = RoleClaimType,
|
||||
NameClaimType = ZbClaimTypes.Name
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
var handler = new JwtSecurityTokenHandler();
|
||||
// MapInboundClaims=false: do NOT let JwtSecurityTokenHandler rewrite
|
||||
// inbound claim types via DefaultInboundClaimTypeMap. The token already
|
||||
// holds the canonical type strings (see GenerateToken's
|
||||
// MapOutboundClaims=false), so reading them back unmapped yields exactly
|
||||
// the strings every policy + claim helper expects.
|
||||
var handler = new JwtSecurityTokenHandler { MapInboundClaims = false };
|
||||
var principal = handler.ValidateToken(token, validationParameters, out _);
|
||||
return principal;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user