Authentication And Authorization JWT در NET 6.0 با Identity Framework

آخرین بروز رسانی: 1401/07/09

به فرآیند تأیید اعتبار کاربر Authentication, میگن و بررسی دسترسی یک کاربر به ماژول های خاص در یک برنامه رو authorization می نامند. در این مقاله نحوه محافظت از برنامه ASP.NET Core Web API را با اجرای JWT authentication خواهیم دید. همچنین نحوه استفاده از authorization در ASP.NET Core را برای کنترل دسترسی به عملکردهای مختلف برنامه خواهیم دید. و با استفاده از Entity Framework و Identity Framework ,اعتبار کاربر را در پایگاه داده server SQL ذخیره میکنیم .
JSON Web Token (JWT) یک استاندارد متن باز (RFC 7519) است که یک روش فشرده و مستقل را برای انتقال ایمن اطلاعات بین طرفین در قالب یک شی JSON تعریف می کند. این اطلاعات به دلیل امضای دیجیتالی قابل تأیید و اعتماد است. JWT ها را می توان با استفاده از یک رمز (با الگوریتم HMAC) یا یک جفت کلید عمومی/خصوصی با استفاده از RSA یا ECDSA امضا کرد.

در شکل فشرده خود، JSON Web Tokens از سه قسمت تشکیل شده است که با نقطه (.) از هم جدا شده اند که عبارتند از:
•    سرتیتر (Header)
•    ظرفیت ترابری (Payload)
•    امضا (Signature)
بنابراین، یک JWT معمولاً به شکل زیر است.
xxxx.yyyy.zzzz

لطفاً برای جزئیات بیشتر در مورد JSON Web Tokens به https://jwt.io/introduction/  مراجعه کنید. 

ASP.NET Core Web API را با استفاده از Visual Studio 2022 ایجاد کنید
برای ایجاد برنامه های NET 6.0 به Visual Studio 2022 نیاز داریم. می توانیم قالب ASP.NET Core Web API را از Visual Studio 2022 انتخاب کنیم.

دانلود کد آموزش

 

 

ما باید 4 کتابخانه زیر را در پروژه جدید نصب کنیم.

•    Microsoft.EntityFrameworkCore.SqlServer
•    Microsoft.EntityFrameworkCore.Tools
•    Microsoft.AspNetCore.Identity.EntityFrameworkCore
•    Microsoft.AspNetCore.Authentication.JwtBearer

 

 

appsettings.json 

حالا  فایل appsettings.json را به شکل زیر تغییر دهیم. دارای جزئیات اتصال دیتابیس و سایر جزئیات برای احراز هویت JWT است.

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "ConnectionStrings": {
    "ConnStr": "Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=JWTAuthDB;Integrated Security=True;ApplicationIntent=ReadWrite;MultiSubnetFailover=False"
  },
  "JWT": {
    "ValidAudience": "http://localhost:4200",
    "ValidIssuer": "http://localhost:5000",
    "Secret": "JWTAuthenticationHIGHsecuredPasswordVVVp1OH7Xzyr"
  }

 

 

ApplicationDbContext.cs 

using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;

namespace JWTAuthentication.NET6._0.Auth
{
    public class ApplicationDbContext : IdentityDbContext<IdentityUser>
    {
        public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options)
        {
        }
        protected override void OnModelCreating(ModelBuilder builder)
        {
            base.OnModelCreating(builder);
        }
    }
}

 

 

RegisterModel.cs 

using System.ComponentModel.DataAnnotations;

namespace JWTAuthentication.NET6._0.Auth
{
    public class RegisterModel
    {
        [Required(ErrorMessage = "User Name is required")]
        public string? Username { get; set; }

        [EmailAddress]
        [Required(ErrorMessage = "Email is required")]
        public string? Email { get; set; }

        [Required(ErrorMessage = "Password is required")]
        public string? Password { get; set; }
    }

 

LoginModel.cs 

using System.ComponentModel.DataAnnotations;

namespace JWTAuthentication.NET6._0.Auth
{
    public class LoginModel
    {
        [Required(ErrorMessage = "User Name is required")]
        public string? Username { get; set; }

        [Required(ErrorMessage = "Password is required")]
        public string? Password { get; set; }
    }
}

 

Response.cs 

برای برگرداندن مقدار پاسخ پس از ثبت نام کاربر و ورود کاربر. همچنین در صورت عدم موفقیت درخواست، پیام های خطا را برمی گرداند.

namespace JWTAuthentication.NET6._0.Auth
{
    public class Response
    {
        public string? Status { get; set; }
        public string? Message { get; set; }
    }
}

 

AuthenticateController.cs 

ما سه متد  “login”، “register” و “register-admin” را در داخل کلاس controller اضافه کرده ایم. register و register-admin تقریباً یکسان هستند، اما از متد register-admin برای ایجاد یک کاربر با نقش مدیر استفاده می شود. در login، پس از ورود موفقیت آمیز، یک توکن JWT را برگردانده ایم.

using JWTAuthentication.NET6._0.Auth;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;

namespace JWTAuthentication.NET6._0.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class AuthenticateController : ControllerBase
    {
        private readonly UserManager<IdentityUser> _userManager;
        private readonly RoleManager<IdentityRole> _roleManager;
        private readonly IConfiguration _configuration;

        public AuthenticateController(
            UserManager<IdentityUser> userManager,
            RoleManager<IdentityRole> roleManager,
            IConfiguration configuration)
        {
            _userManager = userManager;
            _roleManager = roleManager;
            _configuration = configuration;
        }

        [HttpPost]
        [Route("login")]
        public async Task<IActionResult> Login([FromBody] LoginModel model)
        {
            var user = await _userManager.FindByNameAsync(model.Username);
            if (user != null && await _userManager.CheckPasswordAsync(user, model.Password))
            {
                var userRoles = await _userManager.GetRolesAsync(user);

                var authClaims = new List<Claim>
                {
                    new Claim(ClaimTypes.Name, user.UserName),
                    new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
                };

                foreach (var userRole in userRoles)
                {
                    authClaims.Add(new Claim(ClaimTypes.Role, userRole));
                }

               var token = GetToken(authClaims);

                return Ok(new
                {
                    token = new JwtSecurityTokenHandler().WriteToken(token),
                    expiration = token.ValidTo
                });
            }
            return Unauthorized();
        }

        [HttpPost]
        [Route("register")]
        public async Task<IActionResult> Register([FromBody] RegisterModel model)
        {
            var userExists = await _userManager.FindByNameAsync(model.Username);
            if (userExists != null)
                return StatusCode(StatusCodes.Status500InternalServerError, new Response { Status = "Error", Message = "User already exists!" });

            IdentityUser user = new()
            {
                Email = model.Email,
                SecurityStamp = Guid.NewGuid().ToString(),
                UserName = model.Username
            };
            var result = await _userManager.CreateAsync(user, model.Password);
            if (!result.Succeeded)
                return StatusCode(StatusCodes.Status500InternalServerError, new Response { Status = "Error", Message = "User creation failed! Please check user details and try again." });

            return Ok(new Response { Status = "Success", Message = "User created successfully!" });
        }

        [HttpPost]
        [Route("register-admin")]
        public async Task<IActionResult> RegisterAdmin([FromBody] RegisterModel model)
        {
            var userExists = await _userManager.FindByNameAsync(model.Username);
            if (userExists != null)
                return StatusCode(StatusCodes.Status500InternalServerError, new Response { Status = "Error", Message = "User already exists!" });

            IdentityUser user = new()
            {
                Email = model.Email,
                SecurityStamp = Guid.NewGuid().ToString(),
                UserName = model.Username
            };
            var result = await _userManager.CreateAsync(user, model.Password);
            if (!result.Succeeded)
                return StatusCode(StatusCodes.Status500InternalServerError, new Response { Status = "Error", Message = "User creation failed! Please check user details and try again." });

            if (!await _roleManager.RoleExistsAsync(UserRoles.Admin))
                await _roleManager.CreateAsync(new IdentityRole(UserRoles.Admin));
            if (!await _roleManager.RoleExistsAsync(UserRoles.User))
                await _roleManager.CreateAsync(new IdentityRole(UserRoles.User));

            if (await _roleManager.RoleExistsAsync(UserRoles.Admin))
            {
                await _userManager.AddToRoleAsync(user, UserRoles.Admin);
            }
            if (await _roleManager.RoleExistsAsync(UserRoles.Admin))
            {
                await _userManager.AddToRoleAsync(user, UserRoles.User);
            }
            return Ok(new Response { Status = "Success", Message = "User created successfully!" });
        }

        private JwtSecurityToken GetToken(List<Claim> authClaims)
        {
            var authSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["JWT:Secret"]));

            var token = new JwtSecurityToken(
                issuer: _configuration["JWT:ValidIssuer"],
                audience: _configuration["JWT:ValidAudience"],
                expires: DateTime.Now.AddHours(3),
                claims: authClaims,
                signingCredentials: new SigningCredentials(authSigningKey, SecurityAlgorithms.HmacSha256)
                );

            return token;
        }
    }
}

 

 


Program.cs 

از اونجایی که در NET 6.0، مایکروسافت کلاس Startup را حذف کرده و فقط کلاس Program را نگه داشته. ما باید تمام تزریق وابستگی و سایر تنظیمات را در داخل کلاس Program تعریف کنیم.

using JWTAuthentication.NET6._0.Auth;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens;
using System.Text;

var builder = WebApplication.CreateBuilder(args);
ConfigurationManager configuration = builder.Configuration;

// Add services to the container.

// For Entity Framework
builder.Services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(configuration.GetConnectionString("ConnStr")));

// For Identity
builder.Services.AddIdentity<IdentityUser, IdentityRole>()
    .AddEntityFrameworkStores<ApplicationDbContext>()
    .AddDefaultTokenProviders();

// Adding Authentication
builder.Services.AddAuthentication(options =>
{
    options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
    options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})

// Adding Jwt Bearer
.AddJwtBearer(options =>
{
    options.SaveToken = true;
    options.RequireHttpsMetadata = false;
    options.TokenValidationParameters = new TokenValidationParameters()
    {
        ValidateIssuer = true,
        ValidateAudience = true,
        ValidAudience = configuration["JWT:ValidAudience"],
        ValidIssuer = configuration["JWT:ValidIssuer"],
        IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration["JWT:Secret"]))
    };
});

builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

// Authentication & Authorization
app.UseAuthentication();
app.UseAuthorization();

app.MapControllers();

app.Run();

 

قبل از اجرای برنامه باید یک پایگاه داده و جداول مورد نیاز ایجاد کنیم. همانطور که از فریمورک entity استفاده می کنیم، می توانیم از دستور مهاجرت پایگاه داده زیر با کنسول بسته manger برای ایجاد یک اسکریپت مهاجرت استفاده کنیم.

“add-migration Initial” 

“update-database” 

 

تست عملکرد کد
ما می توانیم ویژگی "Authorize" را در کنترلر "WeatherForecast" اضافه کنیم.

 

 

 

 

 

ما می توانیم ک weatherforecastcontroller را با مجوز مبتنی بر نقش تغییر دهیم. (role-based authorization)

 

 

 

 

نتیجه
در این پست نحوه ایجاد یک توکن وب JSON در برنامه ASP.NET Core Web API .NET 6.0 و استفاده از این توکن برای احراز هویت و مجوز را مشاهده کردیم. ما دو کاربر ایجاد کرده ایم، یکی بدون نقش و دیگری با نقش مدیر. ما احراز هویت و مجوز را در سطح کنترل کننده اعمال کرده ایم و رفتارهای متفاوتی را با این دو کاربر مشاهده کرده ایم.

 

 

 

نظر دهید

آدرس ایمیل شما منتشر نخواهد شد. فیلدهای الزامی علامت گذاری شده اند *