دسترسی امن به گیت(gitlab)
- برای مقایسه در اینجا بهتر است از یک loop و بررسی کاراکترها به نحوی که در اینجا بحث شده استفاده کنید. بعد هم حافظه رو تخریب کنید. بحث اصلی اینجا است که قرار است ردی در حافظه باقی نماند؛ آن هم به صورت رمزنگاری نشده.
ASP.NET MVC #18
FormsAuthentication.SetAuthCookie(... // ... FormsAuthentication.RedirectFromLoginPage(...
smc.version = "0"
defaults write com.apple.finder AppleShowAllFiles YES
defaults write com.apple.CoreSimulator.IndigoFramebufferServices FramebufferEmulationHint 1
امکان تست بر روی بر روی آخرین نسخه iOS یعنی 12 بر روی iPhone 5s تا iPhone XS Max وجود دارد. علاوه بر این میتوانید iOS 12 را روی iPad نسل پنج و شش و iPad Air 1 و 2 و iPad Pro تست کنید. اگر قصد تست روی نسخههای قدیمیتر iOS، چون iOS 11 یا سایر دستگاهها را دارید، باید ابتدا در XCode، از منوی Window به Devices and simulators بروید و در تب Simulators روی + کلیک کنید. در قسمت OS version میتوانید نسخههای قدیمیتر را دانلود کنید یا برای Apple TV و Apple Watch نیز Simulator بسازید.
برنامه را روی iPhone 6 یا یک گوشی سبک و ساده دیگر گذاشته و برنامه را اجرا کنید و تست بگیرید. دقت کنید که Simulator روی ویندوز اجرا میشود و نیازی به سوئیچ مداوم بین ویندوز و Mac نیست. ولی اگر Simulator روی ویندوز از لحاظ UI ای عملکرد مناسبی را نداشت، در ویژوال استودیو به منوی Tools رفته و از Options > Xamarin > iOS settings تیک Remote simulator to Windows را بردارید که باعث میشود Simulator در Mac اجرا شود. در هر بار عوض کردن Simulator از گوشی ای به گوشی دیگر، پروژه را Clean - Rebuild کنید.
در سریهای بعد که ویژوال استودیو را باز میکنید، از منوی Tools > iOS گزینه Pair Mac را بزنید و به Mac خود متصل شوید.
برای اینکه بتوانید روی گوشی تست بگیرید، در همین عکس بالا، به جای iPhoneSimulator، گزینه iPhone را انتخاب کنید. بهتر است گوشی به آخرین نسخه iOS آپدیت شده باشد. همچنین iTunes for windows را نصب کنید تا ابتدا ویندوز، گوشی شما را که با کابل به کامپیوتر وصل کردهاید بشناسد. سپس در VM Ware درخواست کنید که گوشی به جای ویندوز، به Virtual Machine مک شما وصل شود. در قسمت پایین - سمت راست VM Ware برای هر سخت افزار متصل به کامپیوتر، یک گزینه هست که آنهایی که پر رنگ هستند، سخت افزارهای متصل به Virtual Mac بوده و کمرنگها را Mac نمیبیند. روی سخت افزار گوشی خود کلیک کنید و Connect را بزنید. حال باید بتوانید در iTunes موجود در Mac، اطلاعات مربوط به گوشی خود را مشاهده کنید.
به ویژوال استودیو برگشته و در قسمت Properties پروژه XamApp.iOS، به تب iOS Bundle signing رفته و ضمن انتخاب Automatic provisioning، در قسمت Team، تیم خود را انتخاب کنید. این Team را در زمان ساخت Apple Developer account ایجاد کردهاید و همانطور که قبلا در این آموزش گفته شد، اگر در ویژوال استودیو با آن لاگین کرده باشید، میتوانید آن را ببینید. در صورتی که Apple Developer account ندارید، بر اساس این آموزش پیش بروید.
زمانیکه برنامه را روی گوشی اجرا میکنید، ممکن است در Mac دیالوگ گرفتن نام کاربری و رمز عبور کاربر Mac باز شود، پس نیم نگاهی به آن داشته باشید. پس از اولین اجرای موفق روی گوشی میتوانید در XCode به منوی Window رفته و سپس Devices & simulators را باز کنید و گوشی خود را در قسمت Devices انتخاب کرده و تیک Connect via network را بزنید تا از این به بعد، بدون کابل نیز بتوانید روی گوشی خود تست کنید. البته گوشی، ویندوز و مک، باید در یک شبکه باشند.
نحوه پابلیش پروژه را در مقاله مربوط به App Center خواهیم نوشت. اما به صورت خلاصه حجم فایل ipa حدود 10 مگ است که شامل تمامی مواردی است که در قسمت Android توضیح دادیم و پروژه نهایی شما حجمی در همین حدود خواهد داشت و با اضافه کردن چندین فرم حجم اضافه نمیشود. همانطور که در قسمت توضیحات پیشرفته پروژه اندروید توضیح دادیم، اینجا نیز از AOT و LLVM برای دستیابی به بالاترین سرعت ممکن کدهای Native استفاده شده و کدهای اضافه از پروژه Link (حذف) میشوند. برای اینکار، در ویژوال استودیو iPhone - Release را انتخاب کنید و پروژه را بیلد کنید. فایل ipa درون Mac در فولدر
Library ▸ Caches ▸ Xamarin ▸ mtbs ▸ builds ▸ XamApp.iOS ▸ e9979ba2348d1c5a87390643d62c4a1b ▸ bin ▸ iPhone ▸ Release ▸ XamApp.iOS.ipa
ساخته میشود که Library فولدری در User شما بوده و مقدار Guid مربوطه، Random است. همچنین XamApp.iOS نام پروژه است.
حالتهای متصور برای عکس بالا عبارتند از [Debug - iPhoneSimulator] و [Debug - iPhone] و [Release - iPhone] که دو تای اول برای تست روی Simulator و Device بوده و حالت سوم برای Release کردن فایل ipa. طبیعی است که تنظیم [Release - Simulator] معنی نمیدهد.
با توجه به اینکه محیط توسعه برنامه را آماده کردهایم، از قسمت بعد، به آموزش کدنویسی خواهیم پرداخت.
منبع ایده و گرافیک طراحی Web & UI
بررسی طول عمر توکنها
اگر مرورگر خود را پس از لاگین به سیستم، برای مدتی به حال خود رها کنید، پس از شروع به کار مجدد، مشاهده خواهید کرد که دیگر نمیتوانید به API دسترسی پیدا کنید. علت اینجا است که Access token صادر شده، منقضی شدهاست. تمام توکنها، دارای طول عمر مشخصی هستند و پس از سپری شدن این زمان، دیگر اعتبارسنجی نخواهند شد. زمان انقضای توکن، در خاصیت یا claim ویژهای به نام exp ذخیره میشود.
در اینجا ما دو نوع توکن را داریم: Identity token و Access token
از Identity token برای ورود به سیستم کلاینت استفاده میشود و به صورت پیشفرض طول عمر کوتاه آن به 5 دقیقه تنظیم شدهاست. علت کوتاه بودن این زمان این است که این توکنها تنها یکبار مورد استفاده قرار میگیرد و پس از ارائهی آن به کلاینت، از طریق آن Claim Identity تولید میشود. پس از آن طول عمر Claim Identity تولید شده صرفا به تنظیمات برنامهی کلاینت مرتبط است و میتواند از تنظیمات IDP کاملا مجزا باشد؛ مانند پیاده سازی sliding expiration. در این حالت تا زمانیکه کاربر در برنامه فعال است، در حالت logged in باقی خواهد ماند.
Access tokenها متفاوت هستند. طول عمر پیشفرض آنها به یک ساعت تنظیم شدهاست و نسبت به Identity token طول عمر بیشتری دارند. پس از اینکه این زمان سپری شد، تنها با داشتن یک Access token جدید است که دسترسی ما مجددا به Web API برقرار خواهد شد. بنابراین در اینجا ممکن است هنوز در برنامهی کلاینت در حالت logged in قرار داشته باشیم، چون هنوز طول عمر Claim Identity آن به پایان نرسیدهاست، اما نتوانیم با قسمتهای مختلف برنامه کار کنیم، چون نمیتوانیم از یک Access token منقضی شده جهت دسترسی به منابع محافظت شدهی سمت Web API استفاده نمائیم. در اینجا دیگر برنامهی کلاینت هیچ نقشی بر روی تعیین طول عمر یک Access token ندارد و این طول عمر صرفا توسط IDP به تمام کلاینتهای آن دیکته میشود.
در اینجا برای دریافت یک Access token جدید، نیاز به یک Refresh token داریم که صرفا برای «کلاینتهای محرمانه» که در قسمت سوم این سری آنها را بررسی کردیم، توصیه میشود.
چگونه میتوان زمان انقضای توکنها را صریحا تنظیم کرد؟
برای تنظیم زمان انقضای توکنها، از کلاس src\IDP\DNT.IDP\Config.cs سمت IDP شروع میکنیم.
namespace DNT.IDP { public static class Config { public static IEnumerable<Client> GetClients() { return new List<Client> { new Client { ClientName = "Image Gallery", // IdentityTokenLifetime = ... // defaults to 300 seconds / 5 minutes // AuthorizationCodeLifetime = ... // defaults to 300 seconds / 5 minutes // AccessTokenLifetime = ... // defaults to 3600 seconds / 1 hour } }; } } }
- مقدار خاصیت AuthorizationCodeLifetime تنظیمات یک کلاینت، عدد صحیحی است با مقدار پیشفرض 300 ثانیه یا معادل 5 دقیقه که طول عمر AuthorizationCode را تعیین میکند. این مورد، طول عمر توکن خاصی نیست و در حین فراخوانی Token Endpoint مبادله میشود و در طی Hybrid flow رخ میدهد. بنابراین مقدار پیشفرض آن بسیار مناسب بوده و نیازی به تغییر آن نیست.
- مقدار خاصیت AccessTokenLifetime تنظیمات یک کلاینت، عدد صحیحی است با مقدار پیشفرض 3600 ثانیه و یا معادل 1 ساعت و طول عمر Access token تولید شدهی توسط این IDP را مشخص میکند.
البته باید درنظر داشت اگر طول عمر این توکن دسترسی را برای مثال به 120 یا 2 دقیقه تنظیم کنید، پس از سپری شدن این 2 دقیقه ... هنوز هم برنامهی کلاینت قادر است به Web API دسترسی داشته باشد. علت آن وجود بازهی 5 دقیقهای است که در طی آن، انجام این عملیات مجاز شمرده میشود و برای کلاینتهایی درنظر گرفته شدهاست که ساعت سیستم آنها ممکن است اندکی با ساعت سرور IDP تفاوت داشته باشند.
درخواست تولید یک Access Token جدید با استفاده از Refresh Tokens
زمانیکه توکنی منقضی میشود، کاربر باید مجددا به سیستم لاگین کند تا توکن جدیدی برای او صادر گردد. برای بهبود این تجربهی کاربری، میتوان در کلاینتهای محرمانه با استفاده از Refresh token، در پشت صحنه عملیات دریافت توکن جدید را انجام داد و در این حالت دیگر کاربر نیازی به لاگین مجدد ندارد. در این حالت برنامهی کلاینت یک درخواست از نوع POST را به سمت IDP ارسال میکند. در این حالت عملیات Client Authentication نیز صورت میگیرد. یعنی باید مشخصات کامل کلاینت را به سمت IDP ارسال کرد. در اینجا اطلاعات هویت کلاینت در هدر درخواست و Refresh token در بدنهی درخواست به سمت سرور IDP ارسال خواهند شد. پس از آن IDP اطلاعات رسیده را تعیین اعتبار کرده و در صورت موفقیت آمیز بودن عملیات، یک Access token جدید را به همراه Identity token و همچنین یک Refresh token جدید دیگر، صادر میکند.
برای صدور مجوز درخواست یک Refresh token، نیاز است scope جدیدی را به نام offline_access معرفی کنیم. به این معنا که امکان دسترسی به برنامه حتی در زمانیکه offline است، وجود داشته باشد. بنابراین offline در اینجا به معنای عدم لاگین بودن شخص در سطح IDP است.
بنابراین اولین قدم پیاده سازی کار با Refresh token، مراجعهی به کلاس src\IDP\DNT.IDP\Config.cs و افزودن خاصیت AllowOfflineAccess با مقدار true به خواص یک کلاینت است:
namespace DNT.IDP { public static class Config { public static IEnumerable<Client> GetClients() { return new List<Client> { new Client { ClientName = "Image Gallery", // IdentityTokenLifetime = ... // defaults to 300 seconds / 5 minutes // AuthorizationCodeLifetime = ... // defaults to 300 seconds / 5 minutes // AccessTokenLifetime = ... // defaults to 3600 seconds / 1 hour AllowOfflineAccess = true, // AbsoluteRefreshTokenLifetime = ... // Defaults to 2592000 seconds / 30 days // RefreshTokenExpiration = TokenExpiration.Sliding UpdateAccessTokenClaimsOnRefresh = true, // ... } }; } } }
- البته RefreshToken ضرورتی ندارد که طول عمر Absolute و یا کاملا تعیین شدهای را داشته باشد. این رفتار را توسط خاصیت RefreshTokenExpiration میتوان به TokenExpiration.Sliding نیز تنظیم کرد. البته حالت پیشفرض آن بسیار مناسب است.
- در اینجا میتوان خاصیت UpdateAccessTokenClaimsOnRefresh را نیز به true تنظیم کرد. فرض کنید یکی از Claims کاربر مانند آدرس او تغییر کردهاست. به صورت پیشفرض با درخواست مجدد توکن توسط RefreshToken، این Claims به روز رسانی نمیشوند. با تنظیم این خاصیت به true این مشکل برطرف خواهد شد.
پس از تنظیم IDP جهت صدور RefreshToken، اکنون کلاس ImageGallery.MvcClient.WebApp\Startup.cs برنامهی MVC Client را به صورت زیر تکمیل میکنیم:
ابتدا در متد تنظیمات AddOpenIdConnect، نیاز است صدور درخواست scope جدید offline_access را صادر کنیم:
options.Scope.Add("offline_access");
در ادامه نیاز است به سرویس ImageGalleryHttpClient مراجعه کرده و کدهای آنرا به صورت زیر تغییر داد:
using System; using System.Collections.Generic; using System.Globalization; using System.Net.Http; using System.Net.Http.Headers; using System.Threading.Tasks; using IdentityModel.Client; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Microsoft.IdentityModel.Protocols.OpenIdConnect; namespace ImageGallery.MvcClient.Services { public interface IImageGalleryHttpClient { Task<HttpClient> GetHttpClientAsync(); } /// <summary> /// A typed HttpClient. /// </summary> public class ImageGalleryHttpClient : IImageGalleryHttpClient { private readonly HttpClient _httpClient; private readonly IConfiguration _configuration; private readonly IHttpContextAccessor _httpContextAccessor; private readonly ILogger<ImageGalleryHttpClient> _logger; public ImageGalleryHttpClient( HttpClient httpClient, IConfiguration configuration, IHttpContextAccessor httpContextAccessor, ILogger<ImageGalleryHttpClient> logger) { _httpClient = httpClient; _configuration = configuration; _httpContextAccessor = httpContextAccessor; _logger = logger; } public async Task<HttpClient> GetHttpClientAsync() { var accessToken = string.Empty; var currentContext = _httpContextAccessor.HttpContext; var expires_at = await currentContext.GetTokenAsync("expires_at"); if (string.IsNullOrWhiteSpace(expires_at) || ((DateTime.Parse(expires_at).AddSeconds(-60)).ToUniversalTime() < DateTime.UtcNow)) { accessToken = await RenewTokens(); } else { accessToken = await currentContext.GetTokenAsync(OpenIdConnectParameterNames.AccessToken); } if (!string.IsNullOrWhiteSpace(accessToken)) { _logger.LogInformation($"Using Access Token: {accessToken}"); _httpClient.SetBearerToken(accessToken); } _httpClient.BaseAddress = new Uri(_configuration["WebApiBaseAddress"]); _httpClient.DefaultRequestHeaders.Accept.Clear(); _httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); return _httpClient; } private async Task<string> RenewTokens() { // get the current HttpContext to access the tokens var currentContext = _httpContextAccessor.HttpContext; // get the metadata var discoveryClient = new DiscoveryClient(_configuration["IDPBaseAddress"]); var metaDataResponse = await discoveryClient.GetAsync(); // create a new token client to get new tokens var tokenClient = new TokenClient( metaDataResponse.TokenEndpoint, _configuration["ClientId"], _configuration["ClientSecret"]); // get the saved refresh token var currentRefreshToken = await currentContext.GetTokenAsync(OpenIdConnectParameterNames.RefreshToken); // refresh the tokens var tokenResult = await tokenClient.RequestRefreshTokenAsync(currentRefreshToken); if (tokenResult.IsError) { throw new Exception("Problem encountered while refreshing tokens.", tokenResult.Exception); } // update the tokens & expiration value var updatedTokens = new List<AuthenticationToken>(); updatedTokens.Add(new AuthenticationToken { Name = OpenIdConnectParameterNames.IdToken, Value = tokenResult.IdentityToken }); updatedTokens.Add(new AuthenticationToken { Name = OpenIdConnectParameterNames.AccessToken, Value = tokenResult.AccessToken }); updatedTokens.Add(new AuthenticationToken { Name = OpenIdConnectParameterNames.RefreshToken, Value = tokenResult.RefreshToken }); var expiresAt = DateTime.UtcNow + TimeSpan.FromSeconds(tokenResult.ExpiresIn); updatedTokens.Add(new AuthenticationToken { Name = "expires_at", Value = expiresAt.ToString("o", CultureInfo.InvariantCulture) }); // get authenticate result, containing the current principal & properties var currentAuthenticateResult = await currentContext.AuthenticateAsync("Cookies"); // store the updated tokens currentAuthenticateResult.Properties.StoreTokens(updatedTokens); // sign in await currentContext.SignInAsync("Cookies", currentAuthenticateResult.Principal, currentAuthenticateResult.Properties); // return the new access token return tokenResult.AccessToken; } } }
پیشتر در قسمت ششم، روش کار مستقیم با DiscoveryClient و TokenClient را در حین کار با UserInfo Endpoint جهت دریافت دستی اطلاعات claims از IDP بررسی کردیم. در اینجا به همین ترتیب با TokenEndpoint کار میکنیم. به همین جهت توسط DiscoveryClient، متادیتای IDP را که شامل آدرس TokenEndpoint است، استخراج کرده و توسط آن TokenClient را به همراه اطلاعات کلاینت تشکیل میدهیم.
سپس مقدار refresh token فعلی را نیاز داریم. زیرا توسط آن است که میتوانیم درخواست دریافت یکسری توکن جدید را ارائه دهیم. پس از آن با فراخوانی tokenClient.RequestRefreshTokenAsync(currentRefreshToken)، تعدادی توکن جدید را از سمت IDP دریافت میکنیم. لیست آنها را تهیه کرده و توسط آن کوکی جاری را به روز رسانی میکنیم. در این حالت نیاز است مجددا SignInAsync فراخوانی شود تا کار به روز رسانی کوکی نهایی گردد.
خروجی این متد، مقدار access token جدید است.
پس از آن در متد GetHttpClientAsync بررسی میکنیم که آیا نیاز است کار refresh token صورت گیرد یا خیر؟ برای این منظور مقدار expires_at را دریافت و با زمان جاری با فرمت UTC مقایسه میکنیم. 60 ثانیه پیش از انقضای توکن، متد RenewTokens فراخوانی شده و توسط آن access token جدیدی برای استفادهی در برنامه صادر میشود. مابقی این متد مانند قبل است و این توکن دسترسی را به همراه درخواست از Web API به سمت آن ارسال میکنیم.
معرفی Reference Tokens
تا اینجا با توکنهایی از نوع JWT کار کردیم. این نوع توکنها، به همراه تمام اطلاعات مورد نیاز جهت اعتبارسنجی آنها در سمت کلاینت، بدون نیاز به فراخوانی مجدد IDP به ازای هر درخواست هستند. اما این نوع توکنها به همراه یک مشکل نیز هستند. زمانیکه صادر شدند، دیگر نمیتوان طول عمر آنها را کنترل کرد. اگر طول عمر یک Access token به مدت 20 دقیقه تنظیم شده باشد، میتوان مطمئن بود که در طی این 20 دقیقه حتما میتوان از آن استفاده کرد و دیگر نمیتوان در طی این بازهی زمانی دسترسی آنرا بست و یا آنرا برگشت زد. اینجاست که Reference Tokens معرفی میشوند. بجای قرار دادن تمام اطلاعات در یک JWT متکی به خود، این نوع توکنهای مرجع، فقط یک Id هستند که به توکن اصلی ذخیره شدهی در سطح IDP لینک میشوند و به آن اشاره میکنند. در این حالت هربار که نیاز به دسترسی منابع محافظت شدهی سمت API را با یک چنین توکن دسترسی لینک شدهای داشته باشیم، Reference Token در پشت صحنه (back channel) به IDP ارسال شده و اعتبارسنجی میشود. سپس محتوای اصلی آن به سمت API ارسال میشود. این عملیات از طریق endpoint ویژهای در IDP به نام token introspection endpoint انجام میشود. به این ترتیب میتوان طول عمر توکن صادر شده را کاملا کنترل کرد؛ چون تنها تا زمانیکه در data store مربوط به IDP وجود خارجی داشته باشند، قابل استفاده خواهند بود. بنابراین نسبت به حالت استفادهی از JWTهای متکی به خود، تنها عیب آن زیاد شدن ترافیک به سمت IDP جهت اعتبارسنجی Reference Tokenها به ازای هر درخواست به سمت Web API است.
چگونه از Reference Tokenها بجای JWTهای متکی به خود استفاده کنیم؟
برای استفادهی از Reference Tokenها بجای JWTها، ابتدا نیاز به مراجعهی به کلاس src\IDP\DNT.IDP\Config.cs و تغییر مقدار خاصیت AccessTokenType هر کلاینت است:
namespace DNT.IDP { public static class Config { public static IEnumerable<Client> GetClients() { return new List<Client> { new Client { ClientName = "Image Gallery", // ... AccessTokenType = AccessTokenType.Reference } }; } } }
اینبار اگر برنامه را اجرا کنید و در کلاس ImageGalleryHttpClient برنامهی کلاینت، بر روی سطر httpClient.SetBearerToken یک break-point قرار دهید، مشاهده خواهید کرد فرمت این توکن ارسالی به سمت Web API تغییر یافته و اینبار تنها یک Id سادهاست که دیگر قابل decode شدن و استخراج اطلاعات دیگری از آن نیست. با ادامه جریان برنامه و رسیدن این توکن به سمت Web API، درخواست رسیده برگشت خواهد خورد و اجرا نمیشود.
علت اینجا است که هنوز تنظیمات کار با token introspection endpoint انجام نشده و این توکن رسیدهی در سمت Web API قابل اعتبارسنجی و استفاده نیست. برای تنظیم آن نیاز است یک ApiSecret را در سطح Api Resource مربوط به IDP تنظیم کنیم:
namespace DNT.IDP { public static class Config { // api-related resources (scopes) public static IEnumerable<ApiResource> GetApiResources() { return new List<ApiResource> { new ApiResource( name: "imagegalleryapi", displayName: "Image Gallery API", claimTypes: new List<string> {"role" }) { ApiSecrets = { new Secret("apisecret".Sha256()) } } }; }
namespace ImageGallery.WebApi.WebApp { public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddAuthentication(defaultScheme: IdentityServerAuthenticationDefaults.AuthenticationScheme) .AddIdentityServerAuthentication(options => { options.Authority = Configuration["IDPBaseAddress"]; options.ApiName = "imagegalleryapi"; options.ApiSecret = "apisecret"; });
اکنون اگر برنامه را اجرا کنید، ارتباط با token introspection endpoint به صورت خودکار برقرار شده، توکن رسیده اعتبارسنجی گردیده و برنامه بدون مشکل اجرا خواهد شد.
چگونه میتوان Reference Tokenها را از IDP حذف کرد؟
هدف اصلی استفادهی از Reference Tokenها به دست آوردن کنترل بیشتری بر روی طول عمر آنها است و حذف کردن آنها میتواند به روشهای مختلفی رخ دهد. برای مثال یک روش آن تدارک یک صفحهی Admin و ارائهی رابط کاربری برای حذف توکنها از منبع دادهی IDP است. روش دیگر آن حذف این توکنها از طریق برنامهی کلاینت با برنامه نویسی است؛ برای مثال در زمان logout شخص. برای این منظور، endpoint ویژهای به نام token revocation endpoint در نظر گرفته شدهاست. فراخوانی آن از سمت برنامهی کلاینت، امکان حذف توکنهای ذخیره شدهی در سمت IDP را میسر میکند.
به همین جهت به کنترلر ImageGallery.MvcClient.WebApp\Controllers\GalleryController.cs مراجعه کرده و متد Logout آنرا تکمیل میکنیم:
namespace ImageGallery.MvcClient.WebApp.Controllers { [Authorize] public class GalleryController : Controller { public async Task Logout() { await revokeTokens(); // Clears the local cookie ("Cookies" must match the name of the scheme) await HttpContext.SignOutAsync("Cookies"); await HttpContext.SignOutAsync("oidc"); } private async Task revokeTokens() { var discoveryClient = new DiscoveryClient(_configuration["IDPBaseAddress"]); var metaDataResponse = await discoveryClient.GetAsync(); var tokenRevocationClient = new TokenRevocationClient( metaDataResponse.RevocationEndpoint, _configuration["ClientId"], _configuration["ClientSecret"] ); var accessToken = await HttpContext.GetTokenAsync(OpenIdConnectParameterNames.AccessToken); if (!string.IsNullOrWhiteSpace(accessToken)) { var response = await tokenRevocationClient.RevokeAccessTokenAsync(accessToken); if (response.IsError) { throw new Exception("Problem accessing the TokenRevocation endpoint.", response.Exception); } } var refreshToken = await HttpContext.GetTokenAsync(OpenIdConnectParameterNames.RefreshToken); if (!string.IsNullOrWhiteSpace(refreshToken)) { var response = await tokenRevocationClient.RevokeRefreshTokenAsync(refreshToken); if (response.IsError) { throw new Exception("Problem accessing the TokenRevocation endpoint.", response.Exception); } } }
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید.
برای اجرای برنامه:
- ابتدا به پوشهی 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 وارد کنید.