یکی از گامهای ابتدایی و مهم در تولید نرمافزار، انتخاب ابزار مناسب برای ذخیرهسازی اطلاعات است. تعیین فرمولی دقیق برای انتخاب ابزار متناسب کاری بسیار مشکل است. علاوه بر آن به دلیل گسترده بودن دامنهی نیازها و تنوع زیاد در گزینههای موجود، گاهی انتخاب حتی برای بهترین برنامهنویسان هم کاری نه چندان آسان به نظر میرسد.
- بر روی پایگاه داده مورد نظر راست کلیک کرده و از گزینه Tasks گزینه Copy Database را انتخاب کنید.
- پس از ظاهر شدن پنجره کپی گزینه Next را انتخاب و در مرحله مبدا و مقصد، سرور جاری را انتخاب کنید و به مرحله بعد بروید.
این برای زمانی است که شما میخواهید پایگاه داده را در سرور دیگری کپی نماید - در پنجره Transfer Method دو روش Detach and Attach و استفاده از SQL Management Object وجود دارد که با همان روش اول به مرحله بعد بروید
- در مرحله بعد نام پایگاه داده شما انتخاب شده به مرحله بعد بروید.
- مرحله بعد پیکریندی پایگاه داده مقصد میباشد که نام و مسیر پایگاه داده جدید را میتوانید مشخص نمایید.
- این عملیات با SQL Server Agent صورت میپذیرد به همین خاطر Agent میبایست نصب و Start شده باشد.
- با انتخاب گزینه Next مراحل بعد را رد کرده تا عملیات آغاز شود.
- در مرحله آخر پایگاه داده قبلی را حذف نمایید.
امن سازی برنامههای ASP.NET Core توسط IdentityServer 4x - قسمت نهم- مدیریت طول عمر توکنها
فروش Auth0 به قیمت 6.5 میلیارد دلار
Blazor 5x - قسمت 23 - احراز هویت و اعتبارسنجی کاربران Blazor Server - بخش 3 - کار با نقشهای کاربران
کار با Authentication State از طریق کدنویسی
فرض کنید در کامپوننت HotelRoomUpsert.razor نمیخواهیم دسترسیها را به کمک اعمال ویژگی attribute [Authorize]@ محدود کنیم؛ میخواهیم اینکار را از طریق کدنویسی مستقیم انجام دهیم:
// ... @*@attribute [Authorize]*@ @code { [CascadingParameter] public Task<AuthenticationState> AuthenticationState { get; set; } protected override async Task OnInitializedAsync() { var authenticationState = await AuthenticationState; if (!authenticationState.User.Identity.IsAuthenticated) { var uri = new Uri(NavigationManager.Uri); NavigationManager.NavigateTo($"/identity/account/login?returnUrl={uri.LocalPath}"); } // ...
- سپس یک پارامتر ویژه را از نوع CascadingParameter، به نام AuthenticationState تعریف کردیم. این خاصیت از طریق کامپوننت CascadingAuthenticationState که در قسمت قبل به فایل BlazorServer.App\App.razor اضافه کردیم، تامین میشود.
- در آخر در روال رویدادگردان OnInitializedAsync، بر اساس آن میتوان به اطلاعات User جاری وارد شدهی به سیستم دسترسی یافت و برای مثال اگر اعتبارسنجی نشده بود، با استفاده از NavigationManager، او را به صفحهی لاگین هدایت میکنیم.
- در اینجا روش ارسال آدرس صفحهی فعلی را نیز مشاهده میکنید. این امر سبب میشود تا پس از لاگین، کاربر مجددا به همین صفحه هدایت شود.
authenticationState، امکانات بیشتری را نیز در اختیار ما قرار میدهد؛ برای مثال با استفاده از متد ()authenticationState.User.IsInRole آن میتوان دسترسی به قسمتی را بر اساس نقشهای خاصی محدود کرد.
ثبت کاربر ادمین Identity
در ادامه میخواهیم دسترسی به کامپوننتهای مختلف را بر اساس نقشها، محدود کنیم. به همین جهت نیاز است تعدادی نقش و یک کاربر ادمین را به بانک اطلاعاتی برنامه اضافه کنیم. برای اینکار به پروژهی BlazorServer.Common مراجعه کرده و تعدادی نقش ثابت را تعریف میکنیم:
namespace BlazorServer.Common { public static class ConstantRoles { public const string Admin = nameof(Admin); public const string Customer = nameof(Customer); public const string Employee = nameof(Employee); } }
سپس در فایل BlazorServer.App\appsettings.json، مشخصات ابتدایی کاربر ادمین را ثبت میکنیم:
{ "AdminUserSeed": { "UserName": "vahid@dntips.ir", "Password": "123@456#Pass", "Email": "vahid@dntips.ir" } }
namespace BlazorServer.Models { public class AdminUserSeed { public string UserName { get; set; } public string Password { get; set; } public string Email { get; set; } } }
namespace BlazorServer.App { public class Startup { public IConfiguration Configuration { get; } public void ConfigureServices(IServiceCollection services) { services.AddOptions<AdminUserSeed>().Bind(Configuration.GetSection("AdminUserSeed")); // ...
using System; using System.Linq; using System.Threading.Tasks; using BlazorServer.Common; using BlazorServer.DataAccess; using BlazorServer.Models; using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Options; namespace BlazorServer.Services { public class IdentityDbInitializer : IIdentityDbInitializer { private readonly ApplicationDbContext _dbContext; private readonly UserManager<IdentityUser> _userManager; private readonly RoleManager<IdentityRole> _roleManager; private readonly IOptions<AdminUserSeed> _adminUserSeedOptions; public IdentityDbInitializer( ApplicationDbContext dbContext, UserManager<IdentityUser> userManager, RoleManager<IdentityRole> roleManager, IOptions<AdminUserSeed> adminUserSeedOptions) { _dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext)); _roleManager = roleManager ?? throw new ArgumentNullException(nameof(roleManager)); _userManager = userManager ?? throw new ArgumentNullException(nameof(userManager)); _adminUserSeedOptions = adminUserSeedOptions ?? throw new ArgumentNullException(nameof(adminUserSeedOptions)); } public async Task SeedDatabaseWithAdminUserAsync() { if (_dbContext.Roles.Any(role => role.Name == ConstantRoles.Admin)) { return; } await _roleManager.CreateAsync(new IdentityRole(ConstantRoles.Admin)); await _roleManager.CreateAsync(new IdentityRole(ConstantRoles.Customer)); await _roleManager.CreateAsync(new IdentityRole(ConstantRoles.Employee)); await _userManager.CreateAsync( new IdentityUser { UserName = _adminUserSeedOptions.Value.UserName, Email = _adminUserSeedOptions.Value.Email, EmailConfirmed = true }, _adminUserSeedOptions.Value.Password); var user = await _dbContext.Users.FirstAsync(u => u.Email == _adminUserSeedOptions.Value.Email); await _userManager.AddToRoleAsync(user, ConstantRoles.Admin); } } }
پس از تعریف این سرویس، نیاز است آنرا به سیستم تزریق وابستگیهای برنامه اضافه کرد:
namespace BlazorServer.App { public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddScoped<IIdentityDbInitializer, IdentityDbInitializer>(); // ...
using System; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Polly; namespace BlazorServer.DataAccess.Utils { public static class MigrationHelpers { public static void MigrateDbContext<TContext>( this IServiceProvider serviceProvider, Action<IServiceProvider> postMigrationAction ) where TContext : DbContext { using var scope = serviceProvider.CreateScope(); var scopedServiceProvider = scope.ServiceProvider; var logger = scopedServiceProvider.GetRequiredService<ILogger<TContext>>(); using var context = scopedServiceProvider.GetService<TContext>(); logger.LogInformation($"Migrating the DB associated with the context {typeof(TContext).Name}"); var retry = Policy.Handle<Exception>().WaitAndRetry(new[] { TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(15) }); retry.Execute(() => { context.Database.Migrate(); postMigrationAction(scopedServiceProvider); }); logger.LogInformation($"Migrated the DB associated with the context {typeof(TContext).Name}"); } } }
کار متد الحاقی فوق، دریافت یک IServiceProvider است که به سرویسهای اصلی برنامه اشاره میکند. سپس بر اساس آن، یک Scoped ServiceProvider را ایجاد میکند تا درون آن بتوان با Context برنامه در طی مدت کوتاهی کار کرد و در پایان آن، سرویسهای ایجاد شده را Dispose کرد.
در این متد ابتدا Database.Migrate فراخوانی میشود تا اگر مرحلهای از Migrations برنامه هنوز به بانک اطلاعاتی اعمال نشده، کار اجرا و اعمال آن انجام شود. سپس یک متد سفارشی را از فراخوان دریافت کرده و اجرا میکند. برای مثال توسط آن میتوان IIdentityDbInitializer در فایل BlazorServer.App\Program.cs به صوت زیر فراخوانی کرد:
public static void Main(string[] args) { var host = CreateHostBuilder(args).Build(); host.Services.MigrateDbContext<ApplicationDbContext>( scopedServiceProvider => scopedServiceProvider.GetRequiredService<IIdentityDbInitializer>() .SeedDatabaseWithAdminUserAsync() .GetAwaiter() .GetResult() ); host.Run(); }
و همچنین کاربر پیشفرض سیستم را نیز میتوان مشاهده کرد:
که نقش ادمین و کاربر پیشفرض، به این صورت به هم مرتبط شدهاند (یک رابطهی many-to-many برقرار است):
محدود کردن دسترسی کاربران بر اساس نقشها
پس از ایجاد کاربر ادمین و تعریف نقشهای پیشفرض، اکنون محدود کردن دسترسی به کامپوننتهای برنامه بر اساس نقشها، سادهاست. برای این منظور فقط کافی است لیست نقشهای مدنظر را که میتوانند توسط کاما از هم جدا شوند، به ویژگی Authorize کامپوننتها معرفی کرد:
@attribute [Authorize(Roles = ConstantRoles.Admin)]
protected override async Task OnInitializedAsync() { var authenticationState = await AuthenticationState; if (!authenticationState.User.Identity.IsAuthenticated || !authenticationState.User.IsInRole(ConstantRoles.Admin)) { var uri = new Uri(NavigationManager.Uri); NavigationManager.NavigateTo($"/identity/account/login?returnUrl={uri.LocalPath}"); }
کدهای کامل این مطلب را از اینجا میتوانید دریافت کنید: Blazor-5x-Part-23.zip
امن سازی برنامههای ASP.NET Core توسط IdentityServer 4x - قسمت سوم - بررسی مفاهیم OpenID Connect
پروتکل OpenID Connect چگونه کار میکند؟
در انتهای قسمت اول این سری، پروتکل OpenID Connect معرفی شد. در ادامه جزئیات بیشتری از این پروتکل را بررسی میکنیم.
هر برنامهی کلاینت متصل به WebAPI مثال قسمت قبل، نیاز به دانستن هویت کاربر وارد شدهی به آنرا دارد. در اینجا به این برنامهی کلاینت، اصطلاحا relying party هم گفته میشود؛ از این جهت که این برنامهی کلاینت، به برنامهی Identity provider و یا به اختصار IDP، جهت دریافت نتیجهی اعتبارسنجی کاربر، وابستهاست. برنامهی کلاینت یک درخواست Authentication را به سمت IDP ارسال میکند. به این ترتیب کاربر به صورت موقت از برنامهی جاری خارج شده و به برنامهی IDP منتقل میشود. در برنامهی IDP است که کاربر مشخص میکند کیست؛ برای مثال با ارائهی نام کاربری و کلمهی عبور. پس از این مرحله، در صورت تائید هویت کاربر، برنامهی IDP یک Identity Token را تولید و امضاء میکند. سپس برنامهی IDP کاربر را مجددا به برنامهی کلاینت اصلی هدایت میکند و Identity Token را در اختیار آن کلاینت قرار میدهد. در اینجا برنامهی کلاینت، این توکن هویت را دریافت و اعتبارسنجی میکند. اگر این اعتبارسنجی با موفقیت انجام شود، اکنون کاربر تعیین اعتبار شده و هویت او جهت استفادهی از قسمتهای مختلف برنامه مشخص میشود. در برنامههای ASP.NET Core، این توکن هویت، پردازش و بر اساس آن یکسری Claims تولید میشوند که در اغلب موارد به صورت یک کوکی رمزنگاری شده در سمت کلاینت ذخیره میشوند.
به این ترتیب مرورگر با هر درخواستی از سمت کاربر، این کوکی را به صورت خودکار به سمت برنامهی کلاینت ارسال میکند و از طریق آن، هویت کاربر بدون نیاز به مراجعهی مجدد به IDP، استخراج و استفاده میشود.
مراحل انتقال کاربر به IDP، صدور توکن هویت، بازگشت مجدد به برنامهی کلاینت، اعتبارسنجی، استخراج Claims و ذخیرهی آن به صورت یک کوکی رمزنگاری شده را در تصویر فوق ملاحظه میکنید. بنابراین در حین کار با یک IDP، مرحلهی لاگین به سیستم، دیگر در برنامه یا برنامههای کلاینت قرار ندارد. در اینجا دو فلش به سمت IDP و سپس به سمت کلاینت را بخاطر بسپارید. در ادامه از آنها برای توضیح Flow و Endpoints استفاده خواهیم کرد.
البته OpenID Connect برای کار همزمان با انواع و اقسام برنامههای کلاینت طراحی شدهاست؛ مانند برنامهی سمت سرور MVC، برنامههای سمت کلاینت جاوا اسکریپتی مانند Angular و برنامههای موبایل. برای این منظور باید در IDP نوع کلاینت و یکسری از تنظیمات مرتبط با آنرا مشخص کرد.
کلاینتهای عمومی و محرمانه
زمانیکه قرار است با یک IDP کار کنیم، این IDP باید بتواند بین یک برنامهی حسابداری و یک برنامهی پرسنلی که از آن برای احراز هویت استفاده میکنند، تفاوت قائل شود و آنها را شناسایی کند.
- کلاینت محرمانه (Confidential Client)
هر کلاینت با یک client-id و یک client-secret شناخته میشود. کلاینتی که بتواند محرمانگی این اطلاعات را حفظ کند، کلاینت محرمانه نامیده میشود.
در اینجا هر کاربر، اطلاعات هویت خود را در IDP وارد میکند. اما اطلاعات تعیین هویت کلاینتها در سمت کلاینتها ذخیره میشوند. برای مثال برنامههای وب ASP.NET Core میتوانند هویت کلاینت خود را به نحو امنی در سمت سرور ذخیره کنند و این اطلاعات، قابل دسترسی توسط کاربران آن برنامه نیستند.
- کلاینت عمومی (Public Client)
این نوع کلاینتها نمیتوانند محرمانگی هویت خود را حفظ و تضمین کنند؛ مانند برنامههای جاوا اسکریپتی Angular و یا برنامههای موبایل که بر روی وسایل الکترونیکی کاربران اجرا میشوند. در این حالت هرچقدر هم سعی کنیم، چون کاربران به اصل این برنامهها دسترسی دارند، نمیتوان محرمانگی اطلاعات ذخیره شدهی در آنها را تضمین کرد.
مفهوم OpenID Connect Endpoints
در تصویر ابتدای بحث، دو فلش را مشاهده میکنید؛ برای مثال چگونه میتوان به Identity token دسترسی یافت (Authentication) و همچنین زمانیکه صحبت از Authorization میشود، چگونه میتوان Access tokens را دریافت کرد. اینکه این مراحله چگونه کار میکنند، توسط Flow مشخص میشود. Flow مشخص میکند که چگونه باید توکنها از سمت IDP به سمت کلاینت بازگشت داده شوند. بسته به نوع کلاینتها که در مورد آنها بحث شد و نیازمندیهای برنامه، باید از Flow مناسبی استفاده کرد.
هر Flow با Endpoint متفاوتی ارتباط برقرار میکند. این Endpointها در حقیقت جایگزین راهحلهای خانگی تولید برنامههای IDP هستند.
- در ابتدا یک Authorization Endpoint وجود دارد که در سطح IDP عمل میکند. این مورد همان انتهای فلش اول ارسال درخواست به سمت IDP است؛ در تصویر ابتدای بحث. کار این Endpoint، بازگشت Identity token جهت انجام عملیات Authentication و بازگشت Access token برای تکمیل عملیات Authorization است. این عملیات نیز توسط Redirection کلاینت انجام میشود (هدایت کاربر به سمت برنامهی IDP، دریافت توکنها و سپس هدایت مجدد به سمت برنامهی کلاینت اصلی).
نکتهی مهم: استفادهی از TLS و یا همان پروتکل HTTPS برای کار با OpenID Connect Endpoints اجباری است و بدون آن نمیتوانید با این سیستم کار کنید. به عبارتی در اینجا ترافیک بین کلاینت و IDP، همواره باید رمزنگاری شده باشد.
البته مزیت کار با ASP.NET Core 2.1، یکپارچگی بهتر و پیشفرض آن با پروتکل HTTPS است؛ تا حدی که مثال پیشفرض local آن به همراه یک مجوز موقتی SSL نصب شدهی توسط SDK آن کار میکند.
- پس از Authorization Endpoint که در مورد آن توضیح داده شد، یک Redirection Endpoint وجود دارد. در ابتدای کار، کلاینت با یک Redirect به سمت IDP هدایت میشود و پس از احراز هویت، مجددا کاربر به سمت کلاینت Redirect خواهد شد. به آدرسی که IDP کاربر را به سمت کلاینت Redirect میکند، Redirection Endpoint میگویند و در سطح کلاینت تعریف میشود. برنامهی IDP، اطلاعات تولیدی خود را مانند انواع توکنها، به سمت این Endpoint که در سمت کلاینت قرار دارد، ارسال میکند.
- پس از آن یک Token Endpoint نیز وجود دارد که در سطح IDP تعریف میشود. این Endpoint، آدرسی است در سمت IDP، که برنامهی کلاینت میتواند با برنامه نویسی، توکنهایی را از آن درخواست کند. این درخواست عموما از نوع HTTP Post بدون Redirection است.
مفهوم OpenID Connect Flow
- اولین Flow موجود، Authorization Code Flow است. کار آن بازگشت کدهای Authorization از Authorization Endpoint و همچنین توکنها از طریق Token Endpoint میباشد. در ایجا منظور از «کدهای Authorization»، اطلاعات دسترسی با طول عمر کوتاه است. هدف Authorization Code این است که مشخص کند، کاربری که به IDP لاگین کردهاست، همانی است که Flow را از طریق برنامهی وب کلاینت، شروع کردهاست. انتخاب این نوع Flow، برای کلاینتهای محرمانه مناسب است. در این حالت میتوان مباحث Refresh token و داشتن توکنهایی با طول عمر بالا را نیز پیاده سازی کرد.
- Implicit Flow، تنها توکنهای تولیدی را توسط Authorization Endpoint بازگشت میدهد و در اینجا خبری از بازگشت «کدهای Authorization» نیست. بنابراین از Token Endpoint استفاده نمیکند. این نوع Flow، برای کلاینتهای عمومی مناسب است. در اینجا کار client authentication انجام نمیشود؛ از این جهت که کلاینتهای عمومی، مناسب ذخیره سازی client-secret نیستند. به همین جهت در اینجا امکان دسترسی به Refresh token و توکنهایی با طول عمر بالا میسر نیست. این نوع از Flow، ممکن است توسط کلاینتهای محرمانه نیز استفاده شود.
- Hybrid Flow، تعدادی از توکنها را توسط Authorization Endpoint و تعدادی دیگر را توسط Token Endpoint بازگشت میدهد؛ بنابراین ترکیبی از دو Flow قبلی است. انتخاب این نوع Flow، برای کلاینتهای محرمانه مناسب است. در این حالت میتوان مباحث Refresh token و داشتن توکنهایی با طول عمر بالا را نیز پیاده سازی کرد. از این نوع Flow ممکن است برای native mobile apps نیز استفاده شود.
آگاهی از انواع Flowها، انتخاب نوع صحیح آنها را میسر میکند که در نتیجه منتهی به مشکلات امنیتی نخواهند شد. برای مثال Hybrid Flow توسط پشتیبانی از Refresh token امکان تمدید توکن جاری و بالا بردن طول عمر آنرا دارد و این طول عمر بالا بهتر است به کلاینتهای اعتبارسنجی شده ارائه شود. برای اعتبارسنجی یک کلاینت، نیاز به client-secret داریم و برای مثال برنامههای جاوا اسکریپتی نمیتوانند محل مناسبی برای ذخیره سازی client-secret باشند؛ چون از نوع کلاینتهای عمومی محسوب میشوند. بنابراین نباید از Hybrid Flow برای برنامههای Angular استفاده کرد. هرچند انتخاب این مساله صرفا به شما بر میگردد و چیزی نمیتواند مانع آن شود. برای مثال میتوان Hybrid Flow را با برنامههای Angular هم بکار برد؛ هرچند ایدهی خوبی نیست.
انتخاب OpenID Connect Flow مناسب برای یک برنامهی کلاینت از نوع ASP.NET Core
برنامههای ASP.NET Core، از نوع کلاینتهای محرمانه بهشمار میروند. بنابراین در اینجا میتوان تمام Flowهای یاد شده را انتخاب کرد. در برنامههای سمت سرور وب، به ویژگی به روز رسانی توکن نیاز است. بنابراین باید دسترسی به Refresh token را نیز داشت که توسط Implicit Flow پشتیبانی نمیشود. به همین جهت از Implicit Flow در اینجا استفاده نمیکنیم. پیش از ارائهی OpenID Connect، تنها Flow مورد استفادهی در برنامههای سمت سرور وب، همان Authorization Code Flow بود. در این Flow تمام توکنها توسط Token Endpoint بازگشت داده میشوند. اما Hybrid Flow نسبت به آن این مزیتها را دارد:
- ابتدا اجازه میدهد تا Identity token را از IDP دریافت کنیم. سپس میتوان آنرا بدون دریافت توکن دسترسی، تعیین اعتبار کرد.
- در ادامه OpenID Connect این Identity token را به یک توکن دسترسی، متصل میکند.
به همین جهت OpenID Connect نسبت به OAuth 2 ارجحیت بیشتری پیدا میکند.
پس از آشنایی با این مقدمات، در قسمت بعدی، کار نصب و راه اندازی IdentityServer را انجام خواهیم داد.
In this video, you will get to experience bringing SQL and Spark together as a unified data platform running on Kubernetes and learn how:
• Data virtualization integrates data from disparate sources, locations and formats, without replicating or moving the data, to create a single "virtual" data layer
• Data Lake - SQL 2019 provides SQL and Spark query capabilities over a scalable storage, across relational and big data
• Data Mart provides an ability to scale out storage for super-fast performance over big data or data from other external sources
مروری بر Claim
Relying party application
هر برنامه سمت client که از Claim پشتیبانی کند
مزایای Claim
- جدا سازی برنامه از جزییات شناسایی
- انعطاف پذیری در احراز هویت
- Single sign-on
- عدم نیاز به VPN
- متحد کردن مجموعه با دیگر شرکت ها
- متحد کردن مجموعه با سرویسهای غیر از AD
عناصر Claim
Claim شامل عناصر زیر میباشد :
- Token
- Claim
- Provider/Issuer
- Sharepoint STS
- ADFS
- ACS
- OID
- ,و غیره