چیستی JWT: یک گذرنامه دیجیتالی امن
به زبان ساده، JWT مانند یک گذرنامه دیجیتالی عمل میکند. این گذرنامه حاوی اطلاعاتی در مورد هویت کاربر و مجوزهای او است که توسط یک نهاد معتبر (معمولاً سرور برنامه) امضا شده است. هنگامی که کاربر میخواهد به یک منبع محافظتشده دسترسی پیدا کند، این گذرنامه را ارائه میدهد و سرور با بررسی امضای آن، هویت کاربر و مجوزهایش را تأیید کرده و در صورت معتبر بودن، اجازه دسترسی را صادر میکند.
.jpeg)
JWT از سه بخش اصلی تشکیل شده است که با نقطه (.) از هم جدا میشوند:
Header (هدر):
این بخش شامل اطلاعاتی در مورد خود توکن، از جمله نوع توکن (که معمولاً JWT است) و الگوریتم امضای استفاده شده (مانند HS256 یا RS256) است. این اطلاعات به صورت JSON کدگذاری شده و سپس با استفاده از الگوریتم Base64Url به یک رشته تبدیل میشود.
برای مثال، یک هدر ساده میتواند به شکل زیر باشد:
{
"alg": "HS256",
"typ": "JWT"
}
که پس از کدگذاری Base64Url به چیزی شبیه به:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
تبدیل میشود.
Payload (بار):
این بخش شامل ادعاها (Claims) است. ادعاها اظهاراتی در مورد یک موجودیت (معمولاً کاربر) و دادههای اضافی هستند.
سه نوع ادعا وجود دارد:
برای مثال، یک payload میتواند به شکل زیر باشد:
{
"sub": "1234567890",
"name": "John Doe",
"admin": true,
"exp": 1682345678
}
که پس از کدگذاری Base64Url به چیزی شبیه به:
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImV4cCI6MTY4MjM0NTY3OH0
تبدیل میشود.
Signature (امضا):
این بخش برای تأیید اصالت توکن و اطمینان از اینکه هیچ کس محتوای آن را پس از صدور تغییر نداده است، استفاده میشود. امضا با ترکیب هدر کدگذاری شده، payload کدگذاری شده و یک کلید مخفی (برای الگوریتمهای متقارن مانند HS256) یا یک کلید خصوصی (برای الگوریتمهای نامتقارن مانند RS256) با استفاده از الگوریتم مشخص شده در هدر، ایجاد میشود.
برای مثال، اگر از الگوریتم HS256 و یک کلید مخفی به نام your-secret-key استفاده شود، امضا به صورت زیر محاسبه میشود:
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
your-secret-key
)
نتیجه این محاسبات یک رشته امضا خواهد بود که به انتهای هدر و payload کدگذاری شده اضافه میشود.
.png)
بنابراین رشته تولیده شده طبق الگوی زیر خواهد بود:
[Base64Url(Header)].[Base64Url(Payload)].[Signature]
مکانیسم مجوزدهی با JWT: گام به گام به سوی امنیت
اکنون بیایید به بررسی دقیق مکانیسم مجوزدهی با استفاده از JWT بپردازیم. این فرآیند معمولاً شامل مراحل زیر است:
دریافت و اعتبارسنجی JWT در سرور:
سرور دریافتکننده درخواست، JWT موجود در هدر Authorization را استخراج میکند. سپس، سرور باید اصالت JWT را با انجام مراحل زیر تأیید کند:
کاربردهای مجوزدهی با JWT: فراتر از احراز هویت
اگرچه JWT اغلب برای احراز هویت استفاده میشود، اما کاربردهای آن فراتر از این است:
مثال در ASP.NET Core MVC: پیادهسازی گام به گام
برای درک بهتر نحوه استفاده از JWT در یک برنامه ASP.NET Core MVC، یک مثال ساده را در نظر میگیریم:
نصب بستههای NuGet: ابتدا باید بستههای NuGet مورد نیاز برای کار با JWT را نصب کنیم. رایجترین بسته System.IdentityModel.Tokens.Jwt و همچنین بسته Microsoft.AspNetCore.Authentication.JwtBearer برای فعال کردن احراز هویت مبتنی بر JWT در ASP.NET Core است. میتوانید این بستهها را از طریق NuGet Package Manager در Visual Studio یا با استفاده از دستورات .NET CLI نصب کنید:
dotnet add package System.IdentityModel.Tokens.Jwt
dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer
ایجاد یک سرویس برای تولید JWT: یک کلاس ایجاد کنید که مسئولیت تولید JWT را بر عهده داشته باشد. این سرویس به یک کلید مخفی برای امضای توکن نیاز دارد. بهتر است این کلید را از طریق تنظیمات برنامه (appsettings.json) پیکربندی کنید.
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using Microsoft.Extensions.Configuration;
using Microsoft.IdentityModel.Tokens;
public class JwtService
{
private readonly string _secretKey;
private readonly string _issuer;
private readonly string _audience;
public JwtService(IConfiguration configuration)
{
_secretKey = configuration["Jwt:SecretKey"];
_issuer = configuration["Jwt:Issuer"];
_audience = configuration["Jwt:Audience"];
}
public string GenerateToken(string userId, string username, List roles)
{
var claims = new List
{
new Claim(ClaimTypes.NameIdentifier, userId),
new Claim(ClaimTypes.Name, username)
};
foreach (var role in roles)
{
claims.Add(new Claim(ClaimTypes.Role, role));
}
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_secretKey));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(
issuer: _issuer,
audience: _audience,
claims: claims,
expires: DateTime.Now.AddHours(1), // زمان انقضا را تنظیم کنید
signingCredentials: creds
);
return new JwtSecurityTokenHandler().WriteToken(token);
}
}
پیکربندی احراز هویت JWT در Startup.cs: در متد ConfigureServices کلاس Startup.cs، سرویس احراز هویت JWT را پیکربندی کنید.
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using System.Text;
public void ConfigureServices(IServiceCollection services)
{
// ... سایر پیکربندیهای سرویس
services.AddSingleton(); // ثبت سرویس JwtService
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = Configuration["Jwt:Issuer"],
ValidAudience = Configuration["Jwt:Audience"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Jwt:SecretKey"]))
};
});
services.AddAuthorization(); // برای استفاده از ویژگیهای [Authorize]
services.AddControllersWithViews();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// ... سایر پیکربندیهای pipeline
app.UseAuthentication(); // فعال کردن مکانیزم احراز هویت
app.UseAuthorization(); // فعال کردن مکانیزم مجوزدهی
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
}
ایجاد یک کنترلر برای احراز هویت و صدور توکن: یک کنترلر ایجاد کنید که endpoint برای احراز هویت کاربر و صدور JWT را ارائه دهد.
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
[ApiController]
[Route("api/[controller]")]
public class AuthController : ControllerBase
{
private readonly JwtService _jwtService;
public AuthController(JwtService jwtService)
{
_jwtService = jwtService;
}
[HttpPost("login")]
public IActionResult Login([FromBody] UserLoginModel model)
{
// در اینجا باید منطق احراز هویت کاربر را پیاده سازی کنید
// برای مثال، بررسی نام کاربری و رمز عبور در پایگاه داده
if (model.Username == "testuser" && model.Password == "password")
{
var userId = "1"; // شناسه کاربر
var username = model.Username;
var roles = new List { "User" }; // نقشهای کاربر
var token = _jwtService.GenerateToken(userId, username, roles);
return Ok(new { Token = token });
}
return Unauthorized();
}
}
public class UserLoginModel
{
public string Username { get; set; }
public string Password { get; set; }
}
محافظت از Endpointها با [Authorize]: برای محافظت از اکشنهای کنترلر یا کل کنترلر، میتوانید از attribute [Authorize] استفاده کنید.
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
[ApiController]
[Route("api/[controller]")]
[Authorize] // برای دسترسی به این کنترلر نیاز به احراز هویت است
public class ProtectedController : ControllerBase
{
[HttpGet("data")]
public IActionResult GetData()
{
return Ok(new { Message = "این داده محافظت شده است و فقط کاربران احراز هویت شده میتوانند به آن دسترسی داشته باشند." });
}
[HttpGet("admin-data")]
[Authorize(Roles = "Admin")] // فقط کاربرانی با نقش "Admin" میتوانند به این اکشن دسترسی داشته باشند
public IActionResult GetAdminData()
{
return Ok(new { Message = "این داده فقط برای مدیران قابل دسترسی است." });
}
}
ارسال توکن از طرف کلاینت: کلاینت پس از دریافت توکن از endpoint /api/Auth/login، باید آن را در هدر Authorization در درخواستهای بعدی به endpointهای محافظتشده ارسال کند. برای مثال، با استفاده از JavaScript در یک برنامه وب:
fetch('/api/protected/data', {
headers: {
'Authorization': 'Bearer ' + localStorage.getItem('authToken') // فرض بر اینکه توکن در localStorage ذخیره شده است
}
})
.then(response => response.json())
.then(data => console.log(data));
نکات امنیتی مهم در استفاده از JWT
از یک کلید مخفی قوی و ایمن استفاده کنید: کلید مخفی (یا کلید خصوصی) برای امضای JWT بسیار حساس است. آن را در یک مکان امن نگهداری کنید و هرگز آن را در سمت کلاینت قرار ندهید
پیاده سازی اختصاصی
حال بیاید فرض کنیم که میخواهیم میکانسیم JWT را بصورت اختصاصی پیاده سازی کنیم بدون آنکه از کتابخانه های رایج و قابل دانلود استفاده کنیم.
پیاده سازی اختصاصی نیازمند فهم و دریافت کامل پروسه از پیش گفته شده در این مقاله خواهد بود.
صرفا جهت یادآوری باید اشاره ای به ساختار JWT داشت:
[Base64Url(Header)].[Base64Url(Payload)].[Signature]
بنابراین جهت ایجاد Payload که حاوی اطلاعات کاربر می باشد نیازمند کلاسی هستیم:
public class UserToken
{
public string UserId { get; set; }
public string Role { get; set; }
public long Exp { get; set; } // زمان انقضا بر اساس Unix
}
ایجاد امضای مخصوص:
public string GenerateKingToken(CustomToken payload, string secretKey)
{
string header = "{\"alg\":\"HS256\",\"typ\":\"JWT\"}";
string encodedHeader = Base64UrlEncode(Encoding.UTF8.GetBytes(header));
string payloadJson = JsonConvert.SerializeObject(payload);
string encodedPayload = Base64UrlEncode(Encoding.UTF8.GetBytes(payloadJson));
string signature = ComputeHMACSHA256($"{encodedHeader}.{encodedPayload}", secretKey);
return $"{encodedHeader}.{encodedPayload}.{signature}";
}
توضیحاتی در خصوص چرایی استفاده از لاین:
string header = "{\"alg\":\"HS256\",\"typ\":\"JWT\"}";
| alg | Description | Type | Symmetric? | ||
| HS256 | HMAC using SHA-256 | HMAC | ✅ Yes | ||
| RS256 |
|
Asymmetric | ❌ No | ||
| ES256 |
|
Asymmetric | ❌ No |
| Type | Security | Use Case | |
| HS256 | Symmetric | Shared secret key | Simpler setup, fast |
| RS256 | Asymmetric | Public/private key | Better separation of roles |
| ES256 | Asymmetric | Smaller, efficient | Mobile, IoT, modern security |
سپس کلاسی جهت بررسی صلاحیت jwt مان مینویسیم:
public bool ValidateKingToken(string token, string secretKey, out UserToken customPayload)
{
customPayload = null;
var parts = token.Split('.');
if (parts.Length != 3) return false;
var header = parts[0];
var payload = parts[1];
var signature = parts[2];
string expectedSignature = ComputeHMACSHA256($"{header}.{payload}", secretKey);
if (signature != expectedSignature) return false;
var jsonPayload = Encoding.UTF8.GetString(Base64UrlDecode(payload));
customPayload = JsonConvert.DeserializeObject<UserToken>(jsonPayload);
var now = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
if (customPayload.Exp < now) return false;
return true;
}
کلاسی جهت تولید الگورتیم: HMAC Helper
private string ComputeHMACSHA256(string text, string key)
{
var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(key));
var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(text));
return Base64UrlEncode(hash);
}
کلاسی جهت تولید Base64Url Helpers:
public string Base64UrlEncode(byte[] input)
{
return Convert.ToBase64String(input)
.TrimEnd('=')
.Replace('+', '-')
.Replace('/', '_');
}
public byte[] Base64UrlDecode(string input)
{
string padded = input
.Replace('-', '+')
.Replace('_', '/');
switch (padded.Length % 4)
{
case 2: padded += "=="; break;
case 3: padded += "="; break;
}
return Convert.FromBase64String(padded);
}
در این اختصاصی سازی قصد استفاده از Cookie را دارم، ولی شما میتوانید از روش های دیگری چون localstorage نیز استفاده کنید:
var tokenPayload = new CustomToken
{
UserId = user.Id.ToString(),
Role = user.Role,
Exp = DateTimeOffset.UtcNow.AddMinutes(30).ToUnixTimeSeconds()
};
string token = GenerateCustomToken(tokenPayload, secretKey);
Response.Cookies.Append("customAuth", token, new CookieOptions
{
HttpOnly = true,
Secure = false, // Set to true in production
SameSite = SameSiteMode.Strict,
Expires = DateTime.UtcNow.AddMinutes(30)
});
و در نهایت نیاز به یک middleware جهت بررسی همیشگی jwt را داریم. البته میتوانید از روش های بیشماری نظیر روش مورد علاقه بنده یعنی Attribute-Based نیز استفاده کنید:
public class CustomTokenMiddleware
{
private readonly RequestDelegate _next;
private readonly string _secretKey;
public CustomTokenMiddleware(RequestDelegate next, IConfiguration config)
{
_next = next;
_secretKey = config["JwtSettings:Key"];
}
public async Task Invoke(HttpContext context)
{
var token = context.Request.Cookies["customAuth"];
if (!string.IsNullOrEmpty(token))
{
if (ValidateCustomToken(token, _secretKey, out var payload))
{
var claims = new[]
{
new Claim(ClaimTypes.NameIdentifier, payload.UserId),
new Claim(ClaimTypes.Role, payload.Role)
};
var identity = new ClaimsIdentity(claims, "CustomScheme");
context.User = new ClaimsPrincipal(identity);
}
}
await _next(context);
}
}
نکته: فعال سازی Middleware در program.cs فراموش نشود:
app.UseMiddleware<CustomTokenMiddleware>();
حال میتوانید از قابلیت های چون [Authorize] و User.Identity.IsAuthenticated بصورت دستی نیز استفاده کنید.
0 نظر
هنوز نظری برای این مقاله ثبت نشده است.