لیست تغییرات جدید ASP.NET Core 5.0
عناوین برخی از امکانات و بهبود ها
- MVC model binding improvements, including support for C# 9 record types
- Blazor Server & Blazor WebAssembly support and improvements
- Built-in OpenAPI and Swagger UI support for Web APIs
- SignalR Hub filters and parallel Hub invocations
- Azure AD authentication with MIcrosoft.Identity.Web
- Auto browser refresh with dotnet watch
- HTTP/2 and gRPC performance improvements
Secure password storage in the Windows Credential Store
Multi-factor authentication support for Visual Studio Team Services.
Two-factor authentication support for GitHub
Personal Access Token generation and usage support for Visual Studio Team Services and GitHub
Non-interactive mode support for Visual Studio Team Services backed by Azure Directory
Optional settings for build agent optimization
Top Issues Fixed in Visual Studio 2019 version 16.2.2
- Fixed Test Explorer doesn't show my tests on VS2019 16.2.0
- Fixed a problem where Visual Studio can stop responding during shutdown.
Security Advisory Notices
CVE-2019-1211 Git for Visual Studio Elevation of Privilege Vulnerability
An elevation of privilege vulnerability exists in Git for Visual Studio when it improperly parses configuration files. An attacker who successfully exploited the vulnerability could execute code in the context of another local user. To exploit the vulnerability, an authenticated attacker would need to modify Git configuration files on a system prior to a full installation of the application. The attacker would then need to convince another user on the system to execute specific Git commands. The update addresses the issue by changing the permissions required to edit configuration files.
In this post I’ll quickly explain the minimum you need to know in order to publish an Angular component to npm. By the end of the post you’ll know how your module to:
- Be platform independent (i.e. run in Web Workers, Universal).
- Should be bundled and distributed.
- Work with the Angular’s Ahead-of-Time compiler.
- Play well with TypeScript and allow autocompletion and compile-time type checking.
امن سازی برنامههای وب همواره چالش برانگیز بودهاست؛ خصوصا این روزها که نیاز است برنامهها، خارج از دیوارهای یک شرکت نیز در دسترس باشند و توسط انواع و اقسام وسایل ارتباطی مورد استفاده قرار گیرند. در سالهای قبل، عموما برنامههای thick clients مانند WPF و WinForms برای شرکتها توسعه داده میشدند و یا برنامههای وب مانند ASP.NET Web Forms که مبتنی بر سرویسها نبودند. در برنامههای ویندوزی، پس از لاگین شخص به شبکه و دومین داخلی شرکت، عموما از روش Windows Authentication برای مشخص سازی سطوح دسترسی کاربران استفاده میشود. در برنامههای Web Forms نیز بیشتر روش Forms Authentication برای اعتبارسنجی کاربران مرسوم است. امن سازی این نوع برنامهها سادهاست. عموما بر روی یک دومین ارائه میشوند و کوکیهای اعتبارسنجی کاربران برای ارائهی مباحثی مانند single sign-on (داشتن تنها یک صفحهی لاگین برای دسترسی به تمام برنامههای شرکت)، میسر است.
عصر شروع بهکارگیری سرویسهای وب
در سالهای اخیر این شیوهی کاری تغییر کرده و بیشتر بر اساس بکارگیری برنامههای مبتنی بر سرویسها شدهاست. برای مثال برای این مورد استاندارد WS-Security مربوط به WCF ارائه شدهاست که باز هم مرتبط است به برنامههای یک دومین و یا یک Application pool. اگر تعداد دومینها بیشتر شوند و نیاز به ارتباط امن بین آنها باشد، استاندارد SAML 2.0 مورد استفاده قرار میگرفت که هدف از آن، انتقال امن اعتبارسنجی و سطوح دسترسی کاربران بین دومینهای مختلف است. همانطور که ملاحظه میکنید تمام این برنامهها و استانداردها، داخل دیوارهای یک شرکت و یک دومین زندگی میکنند.
عصر شروع بهکارگیری Restful-API's
پس از آن باز هم شیوهی طراحی برنامههای تغییر کرد و شروع به ایجاد Restful-API's و HTTP API's کردیم. اینها دیگر الزاما داخل یک دومین ارائه نمیشوند و گاهی از اوقات حتی تحت کنترل ما هم نیستند. همچنین برنامههای ارائه شده نیز دیگر thick clients نیستند و ممکن است برنامههای سمت کلاینت Angular و یا حتی موبایل باشند که مستقیما با سرویسهای API برنامهها کار میکنند. حتی ممکن است یک API را طراحی کنیم که با یک API دیگر کار میکند.
در این حالت دیگر نمیتوان این APIها را با نگهداری آنها داخل دیوارهای یک شرکت محافظت کرد. اگر قرار است با یک HTTP API کار کنیم، این API باید عمومی باشد و در اینجا دیگر نمیتوان از روش Forms Authentication استفاده کرد. زیرا این روش اعتبارسنجی مختص برنامههای سمت سرور قرار گرفتهی در یک دومین طراحی شدهاست و آنچنان سازگاری با برنامههای سمت کلاینت و موبایل خارج از دیوارهای آن ندارد. همچنین ارسال نام کاربری و کلمهی عبور به ازای هر درخواست نیز روشی بسیار بدوی و نا امن است. اینجا است که عصر امن سازی برنامهها به کمک توکنها شروع میشود. با استفادهی از توکنها، بجای هر بار ارسال نام کاربری و کلمهی عبور به ازای هر درخواست از API، صرفا لیست سطوح دسترسی امضا شدهی به امکاناتی خاص، ارسال میشوند.
عصر شروع بهکارگیری Security Tokens
بنابراین در اینجا نیاز به برنامهای برای تولید توکنها و ارسال آنها به کلاینتها داریم. روش متداول پیاده سازی آن، ساخت یک برنامهی ابتدایی، برای دریافت نام کاربری و کلمهی عبور از کاربران و سپس بازگشت یک JSON Web Token به آنها است که بیانگر سطوح دسترسی آنها به قسمتهای مختلف برنامه است و کاربران باید این توکن را به ازای هر درخواستی به سمت سرور (بجای نام کاربری و کلمهی عبور و خود) ارسال کنند.
مشکل این روش در اینجا است که آن برنامهی خاص، باید از نام کاربری و کلمهی عبور کاربران مطلع باشد تا بتواند توکن مناسبی را برای آن کاربر خاص تولید کند. هر چند این روش برای یک تک برنامهی خاص بسیار مناسب به نظر میرسد، اما در یک شرکت، دهها برنامه مشغول به کارند و به اشتراک گذاری نام کاربری و کلمهی عبور کاربران، با تک تک آنها ایدهی مناسبی نیست و پس از مدتی از کنترل خارج خواهد شد. برای مثال کاربری در یک برنامه، کلمهی عبور خود را تغییر میدهد اما در برنامهای دیگر خیر و همین مسالهی عدم هماهنگی بین برنامهها و همچنین بخشهای مختلف یک شرکت، مدیریت یک دست برنامهها را تقریبا غیر ممکن میکند. همچنین در اینجا برنامههای ثالث را نیز باید در نظر داشت که آیا ضروری است آنها به ریز اطلاعات کاربران شرکت دسترسی پیدا کنند؟
به علاوه مشکل دیگر توسعهی این نوع برنامههای صدور توکن خانگی، اختراع مجدد چرخ است. در اینجا برای بهبود امنیت برنامه باید منقضی شدن، تمدید، امضای دیجیتال و اعتبارسنجی توکنها را خودمان پیاده سازی کنیم. توسعهی یک چنین سیستمی اگر غیرممکن نباشد، بسیار سخت و پیچیده است و همچنین باید باگهای امنیتی ممکن را نیز مدنظر داشت.
بنابراین تا اینجا به این نتیجه رسیدهایم که دیگر نمیخواهیم مدیریت نام کاربری و کلمهی عبور کاربران را در سطح هیچکدام از برنامههای خود انجام دهیم و هیچکدام از آنها قرار نیست دریافت کنندهی این اطلاعات باشند. قرار است این کار، به یک تک برنامهی مرکزی مخصوص اینکار منتقل شود و برای اینکار نیاز به پروتکلی امن است که بتوان این توکنهای تولیدی را ارسال و پردازش کرد.
حرکت به سمت «تامین کنندهی هویت مرکزی»
در گذشته، هر تک برنامهای دارای صفحهی لاگین و امکانات مدیریت کاربران آن، تغییر کلمهی عبور، تنظیم مجدد آن و اینگونه عملیات بود. اینروزها دیگر چنین کاری مرسوم نیست. این وظیفهی برنامهی شما نیست که بررسی کند کاربر وارد شدهی به سیستم کیست و آیا ادعای او صحیح است یا خیر؟ این نوع عملیات وظیفهی یک Identity provider و یا به اختصار IDP است. کار IDP اعتبارسنجی کاربر و در صورت نیاز، ارائهی اثبات هویت کاربر، به برنامهی درخواست کنندهاست.
در یک IDP عملیاتی مانند ثبت کاربران و مدیریت آنها انجام میشود. اینجا است که مفاهیمی مانند قفل کردن اکانت و یا تغییر کلمهی عبور و امثال آن انجام میشود و نه اینکه به ازای هر برنامهی تهیه شدهی برای یک شرکت، آن برنامه راسا اقدام به انجام چنین عملیاتی کند. به این ترتیب میتوان به امکان استفادهی مجدد از اطلاعات هویت کاربران و سطوح دسترسی آنها در بین تمام برنامههای موجود رسید.
همچنین با داشتن یک برنامهی IDP خوب پیاده سازی شده، از توزیع باگهای امنیتی مختلف در بین برنامههای گوناگون تهیه شده که هر کدام سیستم امنیتی خاص خودشان را دارند، جلوگیری خواهد شد. برای مثال فرض کنید میخواهید الگوریتم هش کردن پسوردهای سیستم را که امروز نا امن اعلام شدهاست، تغییر دهید. با داشتن یک IDP، دیگر نیازی نیست تا تمام برنامههای خود را برای رفع یک چنین باگهایی، تک تک تغییر دهید.
به علاوه این روزها روش استفادهی از نام کاربری و کلمهی عبور، تنها راه ورود به یک سیستم نیست و استفاده از کلیدهای دیجیتال و یا روشهای ویژهی ابزارهای موبایل نیز به این لیست اضافه شدهاند.
حرکت به سمت استاندارد OAuth 2
OAuth 2.0 پروتکلی است استاندارد، برای Authorization امن کاربران، توسط برنامههای وب، موبایل و دسکتاپ. به این ترتیب میتوان امکان دسترسی یک برنامه را به یک API، به نحوی استاندارد و امن میسر ساخت. OAuth 2.0 یک توکن دسترسی (Access token) را تولید میکند و در اختیار کلاینت قرار میدهد. سپس آن کلاینت با ارسال این توکن به API مدنظر، امکان دسترسی به امکانات مختلف آنرا خواهد یافت. به علاوه چون ماهیت برنامههای کلاینت وب و غیر وب متفاوت است، این استاندارد نحوهی دریافت چنین توکنی را برای برنامههای مختلف نیز تعریف میکند. به این ترتیب موارد مشترکی مانند تولید و نحوهی انتقال توکنها به کلاینتهای مختلف توسط این پروتکل استاندارد بیان میشود. در این حالت راهحلهای خانگی ما تبدیل به راهحلهایی میشوند که استاندارد OAuth 2.0 را پیاده سازی کرده باشند. بنابراین IDP ما باید بر مبنای این استاندارد تهیه شده باشد. برای مثال IdentityServer که در این سری بررسی خواهد شد و یا Azure Active Directory، نمونهای از IDPهایی هستند که این استاندارد را کاملا پیاده سازی کردهاند.
البته باید دقت داشت که این توکنهای دسترسی، تنها سطوح دسترسی به منابع API را مشخص میکنند و یا به عبارتی عملیات Authorization توسط آنها میسر میشود. عملیات ورود به سیستم اصطلاحا Authentication نام دارد و توسط استاندارد دیگری به نام OpenID Connect مدیریت میشود.
حرکت به سمت استاندارد OpenID Connect
OpenID Connect یک لایهی امنیتی بر فراز پروتکل OAuth 2.0 است که به اختصار به آن OIDC نیز گفته میشود. توسط آن یک کلاینت میتواند یک Identity token را علاوه بر Access token درخواست کند. از این Identity token برای ورود به برنامهی کلاینت استفاده میشود (Authentication) و پس از آن، برنامهی کلاینت بر اساس سطوح دسترسی تعریف شدهی در Access token، امکان دسترسی به امکانات مختلف یک API را خواهد یافت (Authorization). همچنین OpenID Connect امکان دسترسی به اطلاعات بیشتری از یک کاربر را نیز میسر میکند.
بنابراین OpenID Connect پروتکلی است که در عمل استفاده میشود و توسعه دهنده و جایگزین کنندهی پروتکل OAuth 2.0 میباشد. هرچند ممکن است در بسیاری از منابع صرفا به OAuth 2.0 بپردازند، اما در پشت صحنه با همان OpenID Connect کار میکنند.
مزیت دیگر کار با OpenID Connect، عدم الزام به استفادهی از API، در برنامهای خاص و یا قدیمی است. اگر برنامهی وب شما با هیچ نوع API ایی کار نمیکند، باز هم میتوانید از امکانات OpenID Connect بهرهمند شوید.
C#.NET for non-engineers.
The first course of "A Sr. Developer Course" courses. which contains:
1- C# Fundamentals for non-engineers.
2- DataBase for non-engineers.
3- Asp.NET WebForm for Non-engineers.
4- Application Architecture for no-engineers.
5- ASP.NET MVC for non-engineers.
6- Angular for non-engineers.
This is a course for who knows noting about C# and development if you know nothing about Array, variable, loop, and conditions you are in the right place.
at the end of this course, we will create one small university registration console application together.
You will learn in this course:
C#.NET
.NET Framework
Methods
Recursive methods
C# Primitive Types/Complex Types
conditions
switch case
Arrays
if statement
switch
loops
Creating a method
ref, out
enums
OOP/Object-oriented programing
Generics
Error handling
problem-solving
working with files
level: beginners to upper intermediate
بررسی اجزای Hybrid Flow
در قسمت سوم در حین «انتخاب OpenID Connect Flow مناسب برای یک برنامهی کلاینت از نوع ASP.NET Core» به این نتیجه رسیدیم که Flow مناسب یک برنامهی Mvc Client از نوع Hybrid است. در اینجا هر Flow، شروع به ارسال درخواستی به سمت Authorization Endpoint میکند؛ با یک چنین قالبی:
https://idpHostAddress/connect/authorize? client_id=imagegalleryclient &redirect_uri=https://clientapphostaddress/signin-oidcoidc &scope=openid profile &response_type=code id_token &response_mode=form_post &nonce=63626...n2eNMxA0
- سپس client_id جهت تعیین برنامهای که درخواست را ارسال میکند، ذکر شدهاست؛ از این جهت که یک IDP جهت کار با چندین نوع کلاینت مختلف طراحی شدهاست.
- redirect_uri همان Redirect Endpoint است که در سطح برنامهی کلاینت تنظیم میشود.
- در مورد scope در قسمت قبل در حین راه اندازی IdentityServer توضیح دادیم. در اینجا برنامهی کلاینت، درخواست scopeهای openid و profile را دادهاست. به این معنا که نیاز دارد تا Id کاربر وارد شدهی به سیستم و همچنین Claims منتسب به او را در اختیار داشته باشد.
- response_type نیز به code id_token تنظیم شدهاست. توسط response_type، نوع Flow مورد استفاده مشخص میشود. ذکر code به معنای بکارگیری Authorization code flow است. ذکر id_token و یا id_token token هر دو به معنای استفادهی از implicit flow است. اما برای مشخص سازی Hybrid flow یکی از سه مقدار code id_token و یا code token و یا code id_token token با هم ذکر میشوند:
- در اینجا response_mode مشخص میکند که اطلاعات بازگشتی از سمت IDP که توسط response_type مشخص شدهاند، با چه قالبی به سمت کلاینت بازگشت داده شوند که میتواند از طریق Form POST و یا URI باشد.
در Hybrid flow با response_type از نوع code id_token، ابتدا کلاینت یک درخواست Authentication را به Authorization Endpoint ارسال میکند (با همان قالب URL فوق). سپس در سطح IDP، کاربر برای مثال با ارائهی کلمهی عبور و نام کاربری، تعیین اعتبار میشود. همچنین در اینجا IDP ممکن است رضایت کاربر را از دسترسی به اطلاعات پروفایل او نیز سؤال بپرسد (تحت عنوان مفهوم Consent). سپس IDP توسط یک Redirection و یا Form POST، اطلاعات authorization code و identity token را به سمت برنامهی کلاینت ارسال میکند. این همان اطلاعات مرتبط با response_type ای است که درخواست کردهایم. سپس برنامهی کلاینت این اطلاعات را تعیین اعتبار کرده و در صورت موفقیت آمیز بودن این عملیات، اکنون درخواست تولید توکن هویت را به token endpoint ارسال میکند. برای این منظور کلاینت سه مشخصهی authorization code ،client-id و client-secret را به سمت token endpoint ارسال میکند. در پاسخ یک identity token را دریافت میکنیم. در اینجا مجددا این توکن تعیین اعتبار شده و سپس Id کاربر را از آن استخراج میکند که در برنامهی کلاینت قابل استفاده خواهد بود. این مراحل را در تصویر زیر میتوانید ملاحظه کنید.
البته اگر دقت کرده باشید، یک identity token در همان ابتدای کار از Authorization Endpoint دریافت میشود. اما چرا از آن استفاده نمیکنیم؟ علت اینجا است که token endpoint نیاز به اعتبارسنجی client را نیز دارد. به این ترتیب یک لایهی امنیتی دیگر نیز در اینجا بکار گرفته میشود. همچنین access token و refresh token نیز از همین token endpoint قابل دریافت هستند.
تنظیم IdentityServer جهت انجام عملیات ورود به سیستم بر اساس جزئیات Hybrid Flow
برای افزودن قسمت لاگین به برنامهی MVC قسمت دوم، نیاز است تغییراتی را در برنامهی کلاینت و همچنین IDP اعمال کنیم. برای این منظور کلاس Config پروژهی IDP را که در قسمت قبل ایجاد کردیم، به صورت زیر تکمیل میکنیم:
namespace DNT.IDP { public static class Config { public static IEnumerable<Client> GetClients() { return new List<Client> { new Client { ClientName = "Image Gallery", ClientId = "imagegalleryclient", AllowedGrantTypes = GrantTypes.Hybrid, RedirectUris = new List<string> { "https://localhost:5001/signin-oidc" }, PostLogoutRedirectUris = new List<string> { "https://localhost:5001/signout-callback-oidc" }, AllowedScopes = { IdentityServerConstants.StandardScopes.OpenId, IdentityServerConstants.StandardScopes.Profile }, ClientSecrets = { new Secret("secret".Sha256()) } } }; } } }
- ابتدا نام کلاینت را مشخص میکنیم. این نام و عنوان، در صفحهی لاگین و Consent (رضایت دسترسی به اطلاعات پروفایل کاربر)، ظاهر میشود.
- همچنین نیاز است یک Id دلخواه را نیز برای آن مشخص کنیم؛ مانند imagegalleryclient در اینجا.
- AllowedGrantTypes را نیز به Hybrid Flow تنظیم کردهایم. علت آنرا در قسمت سوم این سری بررسی کردیم.
- با توجه به اینکه Hybrid Flow از Redirectها استفاده میکند و اطلاعات نهایی را به کلاینت از طریق Redirection ارسال میکند، به همین جهت آدرس RedirectUris را به آدرس برنامهی Mvc Client تنظیم کردهایم (که در اینجا بر روی پورت 5001 کار میکند). قسمت signin-oidc آنرا در ادامه تکمیل خواهیم کرد.
- در قسمت AllowedScopes، لیست scopeهای مجاز قابل دسترسی توسط این کلاینت مشخص شدهاند که شامل دسترسی به ID کاربر و Claims آن است.
- به ClientSecrets نیز جهت client authenticating نیاز داریم.
تنظیم برنامهی MVC Client جهت انجام عملیات ورود به سیستم بر اساس جزئیات Hybrid Flow
برای افزودن قسمت لاگین به سیستم، کلاس آغازین پروژهی MVC Client را به نحو زیر تکمیل میکنیم:
namespace ImageGallery.MvcClient.WebApp { public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddAuthentication(options => { options.DefaultScheme = "Cookies"; options.DefaultChallengeScheme = "oidc"; }).AddCookie("Cookies") .AddOpenIdConnect("oidc", options => { options.SignInScheme = "Cookies"; options.Authority = "https://localhost:6001"; options.ClientId = "imagegalleryclient"; options.ResponseType = "code id_token"; //options.CallbackPath = new PathString("...") //options.SignedOutCallbackPath = new PathString("...") options.Scope.Add("openid"); options.Scope.Add("profile"); options.SaveTokens = true; options.ClientSecret = "secret"; options.GetClaimsFromUserInfoEndpoint = true; });
- ابتدا با فراخوانی AddAuthentication، کار تنظیمات میانافزار استاندارد Authentication برنامههای ASP.NET Core انجام میشود. در اینجا DefaultScheme آن به Cookies تنظیم شدهاست تا عملیات Sign-in و Sign-out سمت کلاینت را میسر کند. سپس DefaultChallengeScheme به oidc تنظیم شدهاست. این مقدار با Scheme ای که در ادامه آنرا تنظیم خواهیم کرد، تطابق دارد.
- سپس متد AddCookie فراخوانی شدهاست که authentication-Scheme را به عنوان پارامتر قبول میکند. به این ترتیب cookie based authentication در برنامه میسر میشود. پس از اعتبارسنجی توکن هویت دریافتی و تبدیل آن به Claims Identity، در یک کوکی رمزنگاری شده برای استفادههای بعدی ذخیره میشود.
- در آخر تنظیمات پروتکل OpenID Connect را ملاحظه میکنید. به این ترتیب مراحل اعتبارسنجی توسط این پروتکل در اینجا که Hybrid flow است، پشتیبانی خواهد شد. اینجا است که کار درخواست Authorization، دریافت و اعتبارسنجی توکن هویت صورت میگیرد. اولین پارامتر آن authentication-Scheme است که به oidc تنظیم شدهاست. به این ترتیب اگر قسمتی از برنامه نیاز به Authentication داشته باشد، OpenID Connect به صورت پیشفرض مورد استفاده قرار میگیرد. به همین جهت DefaultChallengeScheme را نیز به oidc تنظیم کردیم. در اینجا SignInScheme به Cookies تنظیم شدهاست که با DefaultScheme اعتبارسنجی تطابق دارد. به این ترتیب نتیجهی موفقیت آمیز عملیات اعتبارسنجی در یک کوکی رمزنگاری شده ذخیره خواهد شد. مقدار خاصیت Authority به آدرس IDP تنظیم میشود که بر روی پورت 6001 قرار دارد. تنظیم این مسیر سبب خواهد شد تا این میانافزار سمت کلاینت، به discovery endpoint دسترسی یافته و بتواند مقادیر سایر endpoints برنامهی IDP را به صورت خودکار دریافت و استفاده کند. سپس ClientId تنظیم شدهاست که باید با مقدار تنظیم شدهی آن در سمت IDP یکی باشد و همچنین مقدار ClientSecret در اینجا نیز باید با ClientSecrets سمت IDP یکی باشد. ResponseType تنظیم شدهی در اینجا با AllowedGrantTypes سمت IDP تطابق دارد که از نوع Hybrid است. سپس دو scope درخواستی توسط این برنامهی کلاینت که openid و profile هستند در اینجا اضافه شدهاند. به این ترتیب میتوان به مقادیر Id کاربر و claims او دسترسی داشت. مقدار CallbackPath در اینجا به RedirectUris سمت IDP اشاره میکند که مقدار پیشفرض آن همان signin-oidc است. با تنظیم SaveTokens به true امکان استفادهی مجدد از آنها را میسر میکند.
پس از تکمیل قسمت ConfigureServices و انجام تنظیمات میانافزار اعتبارسنجی، نیاز است این میانافزار را نیز به برنامه افزود که توسط متد UseAuthentication انجام میشود:
namespace ImageGallery.MvcClient.WebApp { public class Startup { public void Configure(IApplicationBuilder app, IHostingEnvironment env) { app.UseAuthentication();
پس از این تنظیمات، با اعمال ویژگی Authorize، دسترسی به کنترلر گالری برنامهی MVC Client را صرفا محدود به کاربران وارد شدهی به سیستم میکنیم:
namespace ImageGallery.MvcClient.WebApp.Controllers { [Authorize] public class GalleryController : Controller { // .... public async Task WriteOutIdentityInformation() { var identityToken = await HttpContext.GetTokenAsync(OpenIdConnectParameterNames.IdToken); Debug.WriteLine($"Identity token: {identityToken}"); foreach (var claim in User.Claims) { Debug.WriteLine($"Claim type: {claim.Type} - Claim value: {claim.Value}"); } }
فراخوانی متد GetTokenAsync با پارامتر IdToken، همان Identity token دریافتی از IDP را بازگشت میدهد. این توکن با تنظیم SaveTokens به true در تنظیمات AddOpenIdConnect که پیشتر انجام دادیم، قابل استخراج از کوکی اعتبارسنجی برنامه شدهاست.
این متد را در ابتدای اکشن متد Index فراخوانی میکنیم:
public async Task<IActionResult> Index() { await WriteOutIdentityInformation(); // ....
اجرای برنامه جهت آزمایش تنظیمات انجام شده
برای اجرای برنامه:
- ابتدا به پوشهی src\WebApi\ImageGallery.WebApi.WebApp وارد شده و dotnet_run.bat آنرا اجرا کنید تا WebAPI برنامه راه اندازی شود.
- سپس به پوشهی src\IDP\DNT.IDP مراجعه کرده و و dotnet_run.bat آنرا اجرا کنید تا برنامهی IDP راه اندازی شود.
- در آخر به پوشهی src\MvcClient\ImageGallery.MvcClient.WebApp وارد شده و dotnet_run.bat آنرا اجرا کنید تا MVC Client راه اندازی شود.
اکنون که هر سه برنامه با هم در حال اجرا هستند، مرورگر را گشوده و مسیر https://localhost:5001 را درخواست کنید:
در این حالت چون فیلتر Authorize به کل اکشن متدهای کنترلر گالری اعمال شده، میانافزار Authentication که در فایل آغازین برنامهی کلاینت MVC تنظیم شدهاست، وارد عمل شده و کاربر را به صفحهی لاگین سمت IDP هدایت میکند (شماره پورت آن 6001 است). لاگ این اعمال را هم در برگهی network مرورگر میتواند مشاهده کنید.
در اینجا نام کاربری و کلمهی عبور اولین کاربر تعریف شدهی در فایل Config.cs برنامهی IDP را که User 1 و password است، وارد میکنیم. پس از آن صفحهی Consent ظاهر میشود:
در اینجا از کاربر سؤال میپرسد که آیا به برنامهی کلاینت اجازه میدهید تا به Id و اطلاعات پروفایل و یا همان Claims شما دسترسی پیدا کند؟
فعلا گزینهی remember my design را انتخاب نکنید تا همواره بتوان این صفحه را در دفعات بعدی نیز مشاهده کرد. سپس بر روی گزینهی Yes, Allow کلیک کنید.
اکنون به صورت خودکار به سمت برنامهی MVC Client هدایت شده و میتوانیم اطلاعات صفحهی اول سایت را کاملا مشاهده کنیم (چون کاربر اعتبارسنجی شدهاست، از فیلتر Authorize رد خواهد شد).
همچنین در اینجا اطلاعات زیادی نیز جهت دیباگ برنامه لاگ میشوند که در آینده جهت عیب یابی آن میتوانند بسیار مفید باشند:
با دنبال کردن این لاگ میتوانید مراحل Hybrid Flow را مرحله به مرحله با مشاهدهی ریز جزئیات آن بررسی کنید. این مراحل به صورت خودکار توسط میانافزار Authentication انجام میشوند و در نهایت اطلاعات توکنهای دریافتی به صورت خودکار در اختیار برنامه برای استفاده قرار میگیرند. یعنی هم اکنون کوکی رمزنگاری شدهی اطلاعات اعتبارسنجی کاربر در دسترس است و به اطلاعات آن میتوان توسط شیء this.User، در اکشن متدهای برنامهی MVC، دسترسی داشت.
تنظیم برنامهی MVC Client جهت انجام عملیات خروج از سیستم
ابتدا نیاز است یک لینک خروج از سیستم را به برنامهی کلاینت اضافه کنیم. برای این منظور به فایل Views\Shared\_Layout.cshtml مراجعه کرده و لینک logout را در صورت IsAuthenticated بودن کاربر جاری وارد شدهی به سیستم، نمایش میدهیم:
<div class="navbar-collapse collapse"> <ul class="nav navbar-nav"> <li><a asp-area="" asp-controller="Gallery" asp-action="Index">Home</a></li> <li><a asp-area="" asp-controller="Gallery" asp-action="AddImage">Add an image</a></li> @if (User.Identity.IsAuthenticated) { <li><a asp-area="" asp-controller="Gallery" asp-action="Logout">Logout</a></li> } </ul> </div>
شیء this.User، هم در اکشن متدها و هم در Viewهای برنامه، جهت دسترسی به اطلاعات کاربر اعتبارسنجی شده، در دسترس است.
این لینک به اکشن متد Logout، در کنترلر گالری اشاره میکند که آنرا به صورت زیر تکمیل خواهیم کرد:
namespace ImageGallery.MvcClient.WebApp.Controllers { [Authorize] public class GalleryController : Controller { public async Task Logout() { // Clears the local cookie ("Cookies" must match the name of the scheme) await HttpContext.SignOutAsync("Cookies"); await HttpContext.SignOutAsync("oidc"); }
سپس نیاز است از برنامهی IDP نیز logout شویم. به همین جهت سطر دوم SignOutAsync با پارامتر oidc را مشاهده میکنید. بدون وجود این سطر، کاربر فقط از برنامهی کلاینت logout میشود؛ اما اگر به IDP مجددا هدایت شود، مشاهده خواهد کرد که در آن سمت، هنوز نام کاربری او توسط IDP شناسایی میشود.
بهبود تجربهی کاربری Logout
پس از logout، بدون انجام یکسری از تنظیمات، کاربر مجددا به برنامهی کلاینت به صورت خودکار هدایت نخواهد شد و در همان سمت IDP متوقف میشد. برای بهبود این وضعیت و بازگشت مجدد به برنامهی کلاینت، اینکار را یا توسط مقدار دهی خاصیت SignedOutCallbackPath مربوط به متد AddOpenIdConnect میتوان انجام داد و یا بهتر است مقدار پیشفرض آنرا به تنظیمات IDP نسبت داد که پیشتر در تنظیمات متد GetClients آنرا ذکر کرده بودیم:
PostLogoutRedirectUris = new List<string> { "https://localhost:5001/signout-callback-oidc" },
البته هنوز یک مرحلهی انتخاب و کلیک بر روی لینک بازگشت وجود دارد. برای حذف آن و خودکار کردن Redirect نهایی آن، میتوان کدهای IdentityServer4.Quickstart.UI را که در قسمت قبل به برنامهی IDP اضافه کردیم، اندکی تغییر دهیم. برای این منظور فایل src\IDP\DNT.IDP\Quickstart\Account\AccountOptions.cs را گشوده و سپس فیلد AutomaticRedirectAfterSignOut را که false است، به true تغییر دهید.
تنظیمات بازگشت Claims کاربر به برنامهی کلاینت
به صورت پیشفرض، Identity Server اطلاعات Claims کاربر را ارسال نمیکند و Identity token صرفا به همراه اطلاعات Id کاربر است. برای تنظیم آن میتوان در سمت تنظیمات IDP، در متد GetClients، زمانیکه new Client صورت میگیرد، خاصیت AlwaysIncludeUserClaimsInIdToken هر کلاینت را به true تنظیم کرد؛ اما ایده خوبی نیست. Identity token از طریق Authorization endpoint دریافت میشود. در اینجا اگر این اطلاعات از طریق URI دریافت شود و Claims به Identity token افزوده شوند، به مشکل بیش از حد طولانی شدن URL نهایی خواهیم رسید و ممکن است از طرف وب سرور یک چنین درخواستی برگشت بخورد. به همین جهت به صورت پیشفرض اطلاعات Claims به Identity token اضافه نمیشوند.
در اینجا برای دریافت Claims، یک endpoint دیگر در IDP به نام UserInfo endpoint درنظر گرفته شدهاست. در این حالت برنامهی کلاینت، مقدار Access token دریافتی را که به همراه اطلاعات scopes متناظر با Claims است، به سمت UserInfo endpoint ارسال میکند. باید دقت داشت زمانیکه Identity token دوم از Token endpoint دریافت میشود (تصویر ابتدای بحث)، به همراه آن یک Access token نیز صادر و ارسال میگردد. اینجا است که میانافزار oidc، این توکن دسترسی را به سمت UserInfo endpoint ارسال میکند تا user claims را دریافت کند:
در تنظیمات سمت کلاینت AddOpenIdConnect، درخواست openid و profile، یعنی درخواست Id کاربر و Claims آن وجود دارند:
options.Scope.Add("openid"); options.Scope.Add("profile");
options.GetClaimsFromUserInfoEndpoint = true;
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید.
برای اجرای برنامه:
- ابتدا به پوشهی src\WebApi\ImageGallery.WebApi.WebApp وارد شده و dotnet_run.bat آنرا اجرا کنید تا WebAPI برنامه راه اندازی شود.
- سپس به پوشهی src\IDP\DNT.IDP مراجعه کرده و و dotnet_run.bat آنرا اجرا کنید تا برنامهی IDP راه اندازی شود.
- در آخر به پوشهی src\MvcClient\ImageGallery.MvcClient.WebApp وارد شده و dotnet_run.bat آنرا اجرا کنید تا MVC Client راه اندازی شود.
اکنون که هر سه برنامه با هم در حال اجرا هستند، مرورگر را گشوده و مسیر https://localhost:5001 را درخواست کنید. در صفحهی login نام کاربری را User 1 و کلمهی عبور آنرا password وارد کنید.