پیاده سازی پروژه API در دات نت ، قسمت چهارم ،  RestAPI Authentication Jwt

سلام :)قسمت های قبلی رو اینجا میتونید ببینید.پیاده سازی پروژه API در دات نت ، قسمت اول ، معماری پیاز (Onion Architecture)پیاده سازی پروژه API در دات نت ، قسمت دوم ، ApplicationServices و DataLayerپیاده سازی پروژه API در دات نت ، قسمت سوم ، EndPoint توسط RestAPIدر پایان قسمت قبلی ، عملیات ثبت نام کاربرانمون با موفقیت انجام شد ، در ادامه ی کار قسمت لاگین برنامه رو کار میکنیم.برای لاگین در Api ما از JWT برای اعتبار سنجی کاربر هامون استفاده میکنیم ،در نظر داشته باشید که مبحث JWT مبحث بزرگیه و برای توضیح کامل نیاز به چندین مقاله هستش که از بحث ما خارجه و فقط استفاده ی اون رو توضیح میدیم.خب JWT چجوری کار میکنه؟ به طور خلاصه به این شکل که کاربر که قبلتر عضو شده درخواست لاگین میده بعد از چک کردن نام کاربری و پسورد ، ما به کاربر یک توکن برمیگردونیم (توکن رشته ای از کاراکتر هاست که توسط اون اعتبار سنجی انجام میشه) ، سپس در هر درخواستی که نیاز به اعتبار سنجی داره، توکن رو ارسال میکنیم و پس از معتبر بودن توکن کار ادامه پیدا میکنه.برای کار با JWT باید پکیج زیر رو در پروژه ی API مون نصب کنیم.Microsoft.AspNetCore.Authentication.JwtBearerبعد از نصب یکسری تنظیمات باید انجام بدیم ، کد های زیر رو به فایل Program.cs اضافه میکنیمProgram.csbuilder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidateAudience = true, ValidateLifetime = true, ValidateIssuerSigningKey = true, ValidIssuer = builder.Configuration[&quotJwt:Issuer&quot], ValidAudience = builder.Configuration[&quotJwt:Issuer&quot], IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration[&quotJwt:Key&quot])) }; });در این تکه کد ما توسط AddAuthentication سرویس JWT رو به برنامه مون اضافه میکنیم ، که یکسری Option یا تنظیمات هم می‌گیریه ، ValidateIssuer ValidateAudience ValidateLifetime ValidateIssuerSigningKey رو مقدار true بهشون میدیم ، و مقدار ValidIssuer ValidAudience IssuerSigningKey رو از تنظیمات میخونیم ، تنظیماتی که در فایل appsettings.json اضافه کردیم.appsettings.json &quotJwt&quot: { &quotKey&quot: &quotThisismySecretKey&quot, &quotIssuer&quot: &quotTest.com&quot } }در اینجا نمیخوایم وارد به جزئیات استفاده ار JWT وارد شیم ، تنظیماتی که مقدار true رو دادیم سختگیری بیشتری در چک کردن توکن انجام میده و تظیمات Key همون کلید مورد استفاده ما و Issuer هم به عنوان صادر کننده هستش ، برای تنظیم Key یا کلیدمون میتونیم از یک عبارت پسورد مانند استفاده کنیم و برای Issuer هم از نام سایت یا اپلیکیشن.نکته ی دیگری که هستش در حالت پیشفرض برنامه های Asp Core Web Api در فایل Program.cs میان افزار یا Middleware app.UseAuthorization ، خودش به شکل پیشفرض وجود داره ولی برای استفاده از Authentication باید میان افزار ( Middleware ) UseAuthentication رو قبل از UseAuthorization اضافه کنیم.اینجا یک سوال پیش میاد که تفاوت Authentication و Authorization چیه . شناسايی هويت یا Authentication برای تشخیص کاربر (معتبر بودن نام کاربری، رمز عبور) به کار می رود و Authorization برای تشخیص سطح دسترسی کاربر به اطلاعات می باشد که به منظور جلوگيری از دسترسی افراد غير مجاز به منابع و داده های حفاظت شده می‌باشد.وظیفه Authentication پاسخ به اين سوال است كه كاربر كيست و آيا واقعا همان كسی است كه ادعا می كند؟ ولي Authorization مجموعه مجوز هايی است كه به يک كاربر تشخيص هويت داده شده داده می‌شود و وظيفه اش پاسخ به اين سوال است كه يک كاربر مجاز ( تشخيص هويت داده شده ) چه كار می‌تواند بكند و به چه اطلاعاتی می‌تواند دسترسی داشته باشد.به این شکلapp.UseAuthentication(); app.UseAuthorization();در نهایت فایل Program.cs ما به این شکل میشودProgram.csbuilder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidateAudience = true, ValidateLifetime = true, ValidateIssuerSigningKey = true, ValidIssuer = builder.Configuration[&quotJwt:Issuer&quot], ValidAudience = builder.Configuration[&quotJwt:Issuer&quot], IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration[&quotJwt:Key&quot])) }; });; builder.Services.AddScoped<IUserService, UserService>(); builder.Services.AddScoped<IUserRepository, UserRepository>(); var app = builder.Build(); // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } app.UseHttpsRedirection(); app.UseAuthentication(); app.UseAuthorization(); app.MapControllers(); app.Run();در ادامه میریم برای نوشتن اکشن Login در کنترلر Userابتدا باید یک DTO برای انتقال اطلاعات مربوط به لاگین ایجاد کنیم بنابرین کلاس LoginUserDto رو در داخل پوشه ی Src 01.Core پروژه ه ی ApplicationServices و پوشه ی Models User میسازیمLoginUserDto public class LoginUserDto { public string Mobile { get; set; } public string Password { get; set; } }خب پراپرتی هایی که داریم نام کاربر و پسورد برای عملیات ورود کاربر هستش ، برای اعتبار سنجی مدلمون هم مثل مقاله ی قبلی که فایل AddUserDtoValidation.cs رو برای اعتبار سنجی ثبت کاربرمون ساختیم ،فایل LoginUserDtoValidation رو برای اعتبار سنجی لاگین میسازیم و Rule ها مون رو تعریف میکنیم.LoginUserDtoValidation.cs public class LoginUserDtoValidation : AbstractValidator<LoginUserDto> { public LoginUserDtoValidation() { RuleFor(c => c.Mobile).NotEmpty().WithMessage(&quotبرای موبایل مقدار لازم است&quot) .Length(11).WithMessage(&quotموبایل 11 رقم می‌باشد&quot); RuleFor(c => c.Password).NotEmpty().WithMessage(&quotبرای پسورد مقدار لازم است&quot); } }وارد جزئیات تعریف این کلاس نمیشیم چون در مقاله ی قبلی در ساخت AddUserDtoValidation توضیح دادیم ، در اینجا دو قانون اضافه میکنیم ، یکی برای مقدار داشتن و طول رشته ی نام کاربری ( که همون موبایل هستش ) و یکی هم برای اجباری بودن پسورد.همچنین باید یادمون باشه که این کلاس رو به کلاسی که میخواهیم اعتبار سنجی بشه در فایل Program.cs نسبت بدیم.builder.Services.AddTransient<IValidator<LoginUserDto>, LoginUserDtoValidation>(); خب نوبت به نوشتن عملیات برسی اطلاعات کاربر میرسه ، برای این کار یک متد در اینترفیس IUserService به نام CanLogin و پیاده سازی آن را در سرویس User کلاس UserService اضافه میکنیم .IUserService.cs public interface IUserService { bool CanLogin(LoginUserDto loginModel); }UserService.cs public bool CanLogin(LoginUserDto loginModel) { var user = _userRepository.GetByMobile(loginModel.Mobile); if (user != null) { return Security.SecurePasswordHasher.Verify(loginModel.Password,user.Password); } return false; }در اینجا ورودی ما همان LoginUserDto هستش و خروجی یک Boolean که نشان میدهید اطلاعات ورود صحیح است یا نه ، نکته ای که هستش اینه که ما باید توسط ریپازیتوری User ، کاربر رو توسط شماره موبایل که همون نام کاربری ما هستش دریافت کنیم ، سپس اگر کاربر وجود داشت باید پسورد وارد شده رو با پسورد کاربر رو توسط همون کلاس Security که قبلتر اضافه کردیم چک کنیم که یکی باشد ، در این صورت نام کاربری و پسورد درست هستش و مقدار True برمیگردونیم و در غیر این صورت False.یه مورد دیگه اینه که ما متد GetByMobile رو برای ریپازیتوری User نداریم و باید اضافه کنیم ، همونطور که قبلتر هم گفتیم ممکن هستش ما نیاز به اضافه کردن متد به ریپازیتوریمون داشته باشیم چون متد های پیشفرضی که در IRepository.cs ارث بری و پیاده کردیم جوابگوی کار ما نباشه ، مثل حالت فعلی که نیاز داریم موجودیت کاربر رو بر اساس موبایل بگیریم ، بنابرین میریم تا متد GetByMobile رو به ریپازیتوری User اضافه کنیم.ابتدا متد رو به اینترفیس ریپازیتوری user اضافه میکنیمIUserRepository.cspublic interface IUserRepository : IRepository<User> { User GetByMobile(string mobile); }سپس پیاده سازی اون رو داخل UserRepository.cspublic class UserRepository : EfRepository<User>, IUserRepository { ApplicationContext _dbContext; public UserRepository(ApplicationContext DbContext) : base(DbContext) { _dbContext = DbContext; } public User GetByMobile(string mobile) { return _dbContext.Users.SingleOrDefault(c => c.Mobile == mobile); } }در اینجا توسط کانتکست ار دیتا بیس کاربری که شماره موبایل مورد نظرمون رو داره برمیگردونیم.خب اینجا متد CanLogin سرویس User کامل میشه و میتونیم داخل کنترلر User ازش استفاده کنیم ، به این شکلUserController.cs [HttpPost(&quotLogin&quot)] public IActionResult Login([FromBody] LoginUserDto login) { if (_userService.CanLogin(login)) { var tokenString = GenerateJSONWebToken(login); return Ok(new { token = tokenString }); } else return StatusCode(401); }در اکشن Login چک میکنیم که اطلاعات ورود کاربر درست هستش یا نه اگر درست بود که بهش توکن رو برمیگردونیم تا از اون برای درخواستهای بعدیش استفاده کنه اگر نه هم وضعیت StatusCode(401) یا همون 401 Unauthorized رو برمیگردونیم که به معنی عدم احراز هویت هستش.در اینجا GenerateJSONWebToken برای ما توکن رو میسازه که به شکل زیر هستشGenerateJSONWebToken private string GenerateJSONWebToken(string mobile) { var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config[&quotJwt:Key&quot])); var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256); var claims = new[] { new Claim(JwtRegisteredClaimNames.NameId, mobile), new Claim(JwtRegisteredClaimNames.UniqueName, Guid.NewGuid().ToString()) }; var token = new JwtSecurityToken(_config[&quotJwt:Issuer&quot], _config[&quotJwt:Issuer&quot], claims, expires: DateTime.Now.AddMinutes(60), signingCredentials: credentials); return new JwtSecurityTokenHandler().WriteToken(token); }توسط GenerateJSONWebToken توکن ما ساخته میشه و به کاربر به عنوان خروجی ارسال میشه ، نکته ای که اینجا وجود داره ما تنظیم مقدار زمانی که توکن معتبر هست رو داریم و به اون مقداره یک ساعت یا شصت دقیقه رو دادیم( expires: DateTime.Now.AddMinutes(60) ) که بسته به نیاز قابل تغییر هستش ، البته تکنیکی به نام RefreshToken نیز وجود داره که باعث میشه کمتر نیاز به انجام لاگین توسط کاربر باشه .خب عملیات لاگین ما هم کامل شد و میتونیم تست کنیم برای این کار پروژه رو اجرا میکنیم تا یو آی Swagger برای ما بالا بیاد ، در اینجا میبینیم که یک آیتم دیگه برای در قسمت User اضافه شده به نام Login که همون نام اکشن ما در کنترلر User هستش برای ورود هم میتونیم دوباره ثبت نام دیگه ای کنیم و یا شماره موبایل و پسورد کاربری که قبلا ثبت نام کردیم رو وارد کنیم ، سپس دکمه ی Try it out رو میزنیمدر نتیجه میبینیم که به ما توکن رو برگشت داد که به شکل زیر هستش&quottoken&quot: &quoteyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1laWQiOiIwOTEyMTQ3ODUyNSIsInVuaXF1ZV9uYW1lIjoiZGRhNjM0NDMtMGQ4Zi00NjQ5LTgyNTAtMmQxNDUzODQxYzIwIiwiZXhwIjoxNjU0OTY2NjIzLCJpc3MiOiJUZXN0LmNvbSIsImF1ZCI6IlRlc3QuY29tIn0.2NQl2_pFrXtL72QJFGOzV3M6_1ajS-FT93LuOiHjjQ8&quotدر برنامه توسط این توکن اعتبار سنجی انجام میشه و میتونیم عملیات هایی که نیاز به اعتبار سنجی داره رو توسط این توکن که برای کاربری که لاگین کردیم رو انجام بدیم.ادامه ی کار که اضافه کردن ، ویرایش و حذف موجودیت UserExam یا همون آزمونهای کاربرانمون هستش و نیاز به اعتبار سنجی کاربر رو داره در مقالات بعدی انجام میدیم.البته هنوز از کارمون مونده ولی سورس کامل این مقالات رو میتونید از اینجا ببینید.امیدوارم از این مطلب خوشتون اومده باشه :)پیاده سازی پروژه API در دات نت ، قسمت اول ، معماری پیاز (Onion Architecture)پیاده سازی پروژه API در دات نت ، قسمت دوم ، ApplicationServices و DataLayerپیاده سازی پروژه API در دات نت ، قسمت سوم ، EndPoint توسط RestAPIپیاده سازی پروژه API در دات نت ، قسمت چهارم ، RestAPI Authentication Jwtپیاده سازی پروژه API در دات نت ، قسمت پنجم ، عملیات CRUD در RestAPIپیاده سازی پروژه API در دات نت ، قسمت ششم ( قسمت آخر ) ، عملیات CRUD در RestAPI پارت دوم