رمزنگاری JWT و افزایش امنیت آن در ASP.NET Core
نویسنده: محمد جواد ابراهیمی
تاریخ: ۱۳۹۷/۱۱/۱۸ ۸:۴۵
آدرس: www.dntips.ir
| مطالب | ۳۶۹۴ |
| نویسندگان | ۲۷۶ |
| گروههای مطالب | ۱۰۲۴ |
| نقشههای راه | ۱۱۹ |
| دورهها | ۱۴ |
| اشتراکها | ۱۷۹۱۴ |
var secretKey = Encoding.UTF8.GetBytes("LongerThan-16Char-SecretKey"); // must be 16 character or longer
var signingCredentials = new SigningCredentials(new SymmetricSecurityKey(secretKey), SecurityAlgorithms.HmacSha256Signature);
var encryptionkey = Encoding.UTF8.GetBytes("16CharEncryptKey"); //must be 16 character
var encryptingCredentials = new EncryptingCredentials(new SymmetricSecurityKey(encryptionkey), SecurityAlgorithms.Aes128KW, SecurityAlgorithms.Aes128CbcHmacSha256);
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, "UserName"), //user.UserName
new Claim(ClaimTypes.NameIdentifier, "123"), //user.Id
};
var descriptor = new SecurityTokenDescriptor
{
Issuer = _siteSetting.JwtSettings.Issuer,
Audience = _siteSetting.JwtSettings.Audience,
IssuedAt = DateTime.Now,
NotBefore = DateTime.Now.AddMinutes(_siteSetting.JwtSettings.NotBeforeMinutes),
Expires = DateTime.Now.AddMinutes(_siteSetting.JwtSettings.ExpirationMinutes),
SigningCredentials = signingCredentials,
EncryptingCredentials = encryptingCredentials,
Subject = new ClaimsIdentity(claims)
};
var tokenHandler = new JwtSecurityTokenHandler();
var securityToken = tokenHandler.CreateToken(descriptor);
string encryptedJwt = tokenHandler.WriteToken(securityToken);
در ادامه لازم است در مرحله اعتبار سنجی و رمزگشایی توکن در سمت سرور، کلید و الگوریتم لازم را به آن معرفی کنیم تا middleware مربوطه بتواند توکن دریافتی را رمزگشایی و سپس اعتبار سنجی کند. بدین منظور در متد ConfigureServices کلاس Startup.cs خواهیم داشت:
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
var secretkey = Encoding.UTF8.GetBytes("LongerThan-16Char-SecretKey");
var encryptionkey = Encoding.UTF8.GetBytes("16CharEncryptKey");
var validationParameters = new TokenValidationParameters
{
ClockSkew = TimeSpan.Zero, // default: 5 min
RequireSignedTokens = true,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(secretkey),
RequireExpirationTime = true,
ValidateLifetime = true,
ValidateAudience = true, //default : false
ValidAudience = "MyWebsite",
ValidateIssuer = true, //default : false
ValidIssuer = "MyWebsite",
TokenDecryptionKey = new SymmetricSecurityKey(encryptionkey)
};
options.RequireHttpsMetadata = false;
options.SaveToken = true;
options.TokenValidationParameters = validationParameters;
}); کد بالا مانند کد فعال سازی احراز هویت توسط JWT معمولی در ASP.NET Core است؛ با این تفاوت که:
ابتدا آرایه بایتی همان کلید رمزنگاری (encryptionkey) که قبلا توکن را با آن رمزنگاری کرده بودیم، گرفته شده و سپس توسط مقداردهی خاصیت TokenDecryptionKey کلاس TokenValidationParameters، معرفی شده است.
ولی شاید این سؤال برایتان پیش آید که چرا الگوریتم رمزنگاری مشخص نشده است؟ پس سرور از کجا میفهمد که این توکن بر اساس چه الگوریتمی رمزنگاری شده است؟
دلیل آن این است که به هنگام تولید توکن، اسم الگوریتم مربوطه، داخل بخش header توکن نوشته میشود. اگر تصویر قبل را مشاهده کنید مقدار header توکن به شرح زیر است.
{
"alg": "A128KW",
"enc": "A128CBC-HS256",
"typ": "JWT"
} پس سرور بر اساس این قسمت از توکن (header)، که هیچگاه رمزنگاری نمیشود، میفهمد که توسط چه الگوریتمی باید توکن را رمزگشایی کند که در اینجا A128CBC-HS256 (اختصار AES-128-CBC و HMAC-SHA256) است.
مثال کامل و قابل اجرای این مطلب را میتوانید از این ریپازیتوری دریافت کنید.
if (xhr.status === 401) {
alert('Access denied! Please login with an account which has enough permissions first.');
}
.AddJwtBearer(options =>
{
//...
options.TokenValidationParameters = validationParameters;
options.Events = new JwtBearerEvents
{
OnAuthenticationFailed = context =>
{
//var logger = context.HttpContext.RequestServices.GetRequiredService<ILoggerFactory>().CreateLogger(nameof(JwtBearerEvents));
//logger.LogError("Authentication failed.", context.Exception);
if (context.Exception != null)
{
//context.HttpContext.Response.StatusCode = 401;
await context.HttpContext.Response.WriteAsync("خطا در اجراز هویت");
}
}
}
} public interface IJwtService
{
Task CheckId(int id, ClaimsPrincipal claimsPrincipal);
}
public class JwtService : IJwtService
{
private readonly IUserService _userService;
public JwtService(IUserService userService)
{
_userService = userService;
}
public async Task CheckId(int id, ClaimsPrincipal claimsPrincipal)
{
var jwtId = Convert.ToInt32(claimsPrincipal.Identity.FindFirstValue(ClaimTypes.NameIdentifier));
if (jwtId != id)
{
var user = _userService.GetById(jwtId);
user.SecurityStamp = Guid.NewGuid();
await _userService.UpdateAsync(user);
throw new Exception("You are unauthorized to access this resource.");
}
}
} private readonly IJwtService _jwtService;
private readonly IUserService _userService;
public UserController(IJwtService jwtService, IUserService userService)
{
_jwtService = jwtService;
_userService = userService;
}
[HttpPut("Update")]
public async Task<IActionResult> Update(UserEditViewModel editViewModel, CancellationToken cancellationToken)
{
_jwtService.CheckId(editViewModel.Id, HttpContext.User);
await _userService.Update(editViewModel, cancellationToken);
}