האיור אינו זמין: סמל ASP.NET Core (מקור התמונה המקורי).
כל הקוד נועד להמחשה ולא הורץ.
יצירת אסימון חתום להמחשה
למערכת כניסה או גישה מואצלת אמיתית יש להשתמש במנפיק OAuth/OIDC מבוסס. הקוד כאן מדגים חוזה JWT מצומצם למערכת סגורה, ולא שרת הרשאות תקני מלא. הקישור המקורי ל־KeyStore Explorer נשמר כהפניה לכלי תעודות, ולא כהנחיה ליצור או להתקין אישור גישה לייצור.
הצד המנפיק צריך מפתח RSA פרטי הנגיש לפי מדיניות ניהול מפתחות מאושרת. מעבירים אותו כמופע X509Certificate2; המאמת צריך רק את התעודה הציבורית המהימנה. בפרויקט .NET החבילה System.IdentityModel.Tokens.Jwt מספקת את טיפוסי האסימון שלהלן. יש להשתמש בגרסאות תלויות תואמות ומקובעות.
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using Microsoft.IdentityModel.Tokens;
public static class ExampleTokenIssuer
{
// Closed-system illustration, not a replacement for an OAuth/OIDC server.
public static string Create(X509Certificate2 signingCertificate,
string issuer, string audience, string subject)
{
if (string.IsNullOrWhiteSpace(issuer) || string.IsNullOrWhiteSpace(audience)
|| string.IsNullOrWhiteSpace(subject))
throw new ArgumentException("Issuer, audience and subject are required.");
using RSA? rsa = signingCertificate.GetRSAPrivateKey();
if (rsa is null || rsa.KeySize < 2048)
throw new ArgumentException("An RSA private key of at least 2048 bits is required.");
var now = DateTime.UtcNow;
if (signingCertificate.NotBefore.ToUniversalTime() > now
|| signingCertificate.NotAfter.ToUniversalTime() <= now.AddMinutes(5))
throw new ArgumentException("Signing certificate does not cover the token lifetime.");
var descriptor = new SecurityTokenDescriptor
{
Issuer = issuer,
Audience = audience,
IssuedAt = now,
NotBefore = now,
Expires = now.AddMinutes(5),
Claims = new Dictionary<string, object>
{
[JwtRegisteredClaimNames.Sub] = subject,
[JwtRegisteredClaimNames.Jti] = Guid.NewGuid().ToString("N")
},
SigningCredentials = new SigningCredentials(
new X509SecurityKey(signingCertificate), SecurityAlgorithms.RsaSha256)
};
var handler = new JwtSecurityTokenHandler();
return handler.WriteToken(handler.CreateToken(descriptor));
}
}המנפיק, קהל היעד והנושא הם קלטים מפורשים, לא מצייני מקום אופציונליים. הפונקציה מוסיפה זמן הנפקה, תחילת תוקף, תפוגה ומזהה אסימון. מזהה אסימון לבדו אינו מונע שימוש חוזר: אפשר להשתמש שוב באסימון bearer רגיל עד לפקיעתו, אלא אם מופעלות בקרות נוספות. יש להגן על אסימונים בתעבורה באמצעות HTTPS ולעולם לא לרשום אותם ביומן.
אכיפת אימות JWT Bearer
מתודת ההרחבה דורשת כארגומנטים את התעודה הציבורית המהימנה ואת המנפיק וקהל היעד הקבועים. טוענים אותם מתצורת פריסה שבשליטת מנהל, ומכשילים את האתחול אם אינם זמינים. שומרים את התעודה זמינה לאורך חיי השירות. תעודה, נתיב מפתח או מנפיק שמספקת בקשה אינם נעשים מהימנים אוטומטית.
using System;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.IdentityModel.Tokens;
public static class PinnedJwtAuthentication
{
public static IServiceCollection AddPinnedJwt(this IServiceCollection services,
X509Certificate2 trustedPublicCertificate, string issuer, string audience)
{
if (string.IsNullOrWhiteSpace(issuer) || string.IsNullOrWhiteSpace(audience))
throw new ArgumentException("Issuer and audience are required.");
using RSA? rsa = trustedPublicCertificate.GetRSAPublicKey();
if (rsa is null || rsa.KeySize < 2048)
throw new ArgumentException("Expected a trusted RSA public certificate.");
var key = new X509SecurityKey(trustedPublicCertificate);
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.MapInboundClaims = false;
options.IncludeErrorDetails = false;
options.TokenValidationParameters = new TokenValidationParameters
{
RequireSignedTokens = true,
ValidateIssuerSigningKey = true,
IssuerSigningKey = key,
ValidAlgorithms = new[] { SecurityAlgorithms.RsaSha256 },
ValidateIssuer = true,
ValidIssuer = issuer,
ValidateAudience = true,
ValidAudience = audience,
ValidateLifetime = true,
RequireExpirationTime = true,
ClockSkew = TimeSpan.FromSeconds(30),
NameClaimType = "sub"
};
});
services.AddAuthorization(options =>
{
options.FallbackPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser().Build();
});
return services;
}
}הקוד המקורי הגדיר את ValidateIssuer ל־true ואז דרס אותו ב־false. גרסה זו אינה עושה זאת. היא מאמתת חתימה, רשימת אלגוריתמים מותרת מפורשת, מנפיק, קהל יעד ותפוגה. קיבוע התעודה הוא מודל אמון מכוון; יש לתכנן בנפרד חידוש תעודה, הסרה בעקבות פשרה ומדיניות ביטול. מיפוי השם מוגדר במפורש כ־sub, בלי להסתמך על ברירות מחדל לא מוגדרות.
מגינים על נקודות הקצה, לא רק על צינור התווכה
אובייקט User שאינו null אינו הוכחת אימות. מדיניות ברירת המחדל והדרישה המפורשת לנקודת הקצה שלהלן משתמשות ב־RequireAuthenticatedUser. פעולות עסקיות עדיין דורשות בדיקות scope, תפקיד או בעלות על משאב משלהן; הדוגמאות אינן מעניקות תפקיד רק בשל נוכחות אסימון.
// After AddPinnedJwt(...) has been called with approved deployment inputs:
app.UseAuthentication();
app.UseAuthorization();
app.MapGet("/private", () => "Authenticated endpoint").RequireAuthorization();המשתנה app הוא ה־WebApplication הקיים אחרי רישום השירותים והבנייה. הנתיב בדוגמה הוא נתיב מקומי להמחשה, לא כתובת חיצונית. לא הונפקו אסימונים ולא שונו מפתח, תעודה או תצורת שרת. הקוד לא הודר ולא הורץ.
מקורות ראשוניים
- Microsoft JWT bearer configuration
- RFC 7519 JWT
- RFC 8725 JWT security practices
- Microsoft claims mapping
- Microsoft simple authorization
- KeyStore Explorer
המאמר המקורי באנגלית: Configuring JWT Authentication with ASP.NET Core. תאריך הפרסום המקורי: 2021-08-11T16:52:00.009+03:00.