کتاب درک رمزنگاری اطلاعات
Cryptography has crept into everything, from web browsers and email programs to cell phones, bank cards, cars and even into medical implants. Thus, an increasing number of people have to understand how crypto schemes work and how they can be used in practice. We wanted to create a book that teaches modern applied cryptography to readers with a technical background but without an education in pure mathematics.
امن سازی برنامههای ASP.NET Core توسط IdentityServer 4x - قسمت دوازدهم- یکپارچه سازی با اکانت گوگل
ثبت یک برنامهی جدید در گوگل
اگر بخواهیم از گوگل به عنوان یک IDP ثالث در IdentityServer استفاده کنیم، نیاز است در ابتدا برنامهی IDP خود را به آن معرفی و در آنجا ثبت کنیم. برای این منظور مراحل زیر را طی خواهیم کرد:
1- مراجعه به developer console گوگل و ایجاد یک پروژهی جدید
https://console.developers.google.com
در صفحهی باز شده، بر روی دکمهی select project در صفحه و یا لینک select a project در نوار ابزار آن کلیک کنید. در اینجا دکمهی new project و یا create را مشاهده خواهید کرد. هر دوی این مفاهیم به صفحهی زیر ختم میشوند:
در اینجا نامی دلخواه را وارد کرده و بر روی دکمهی create کلیک کنید.
2- فعالسازی API بر روی این پروژهی جدید
در ادامه بر روی لینک Enable APIs And Services کلیک کنید و سپس google+ api را جستجو نمائید.
پس از ظاهر شدن آن، این گزینه را انتخاب و در صفحهی بعدی، آنرا با کلیک بر روی دکمهی enable، فعال کنید.
3- ایجاد credentials
در اینجا بر روی دکمهی create credentials کلیک کرده و در صفحهی بعدی، این سه گزینه را با مقادیر مشخص شده، تکمیل کنید:
• Which API are you using? – Google+ API • Where will you be calling the API from? – Web server (e.g. node.js, Tomcat) • What data will you be accessing? – User data
• نام: همان مقدار پیشفرض آن
• Authorized JavaScript origins: آنرا خالی بگذارید.
• Authorized redirect URIs: این مورد همان callback address مربوط به IDP ما است که در اینجا آنرا با آدرس زیر مقدار دهی خواهیم کرد.
https://localhost:6001/signin-google
سپس در ذیل این صفحه بر روی دکمهی «Create OAuth 2.0 Client ID» کلیک کنید تا به صفحهی «Set up the OAuth 2.0 consent screen» بعدی هدایت شوید. در اینجا دو گزینهی آنرا به صورت زیر تکمیل کنید:
- Email address: همان آدرس ایمیل واقعی شما است.
- Product name shown to users: یک نام دلخواه است. نام برنامهی خود را برای نمونه ImageGallery وارد کنید.
برای ادامه بر روی دکمهی Continue کلیک نمائید.
4- دریافت credentials
در پایان این گردش کاری، به صفحهی نهایی «Download credentials» میرسیم. در اینجا بر روی دکمهی download کلیک کنید تا ClientId و ClientSecret خود را توسط فایلی به نام client_id.json دریافت نمائید.
سپس بر روی دکمهی Done در ذیل صفحه کلیک کنید تا این پروسه خاتمه یابد.
تنظیم برنامهی IDP برای استفادهی از محتویات فایل client_id.json
پس از پایان عملیات ایجاد یک برنامهی جدید در گوگل و فعالسازی Google+ API در آن، یک فایل client_id.json را دریافت میکنیم که اطلاعات آن باید به صورت زیر به فایل آغازین برنامهی IDP اضافه شود:
الف) تکمیل فایل src\IDP\DNT.IDP\appsettings.json
{ "Authentication": { "Google": { "ClientId": "xxxx", "ClientSecret": "xxxx" } } }
ب) تکمیل اطلاعات گوگل در کلاس آغازین برنامه
namespace DNT.IDP { public class Startup { public void ConfigureServices(IServiceCollection services) { // ... services.AddAuthentication() .AddGoogle(authenticationScheme: "Google", configureOptions: options => { options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme; options.ClientId = Configuration["Authentication:Google:ClientId"]; options.ClientSecret = Configuration["Authentication:Google:ClientSecret"]; }); }
- authenticationScheme تنظیم شده باید یک عبارت منحصربفرد باشد.
- همچنین SignInScheme یک چنین مقداری را در اصل دارد:
public const string ExternalCookieAuthenticationScheme = "idsrv.external";
آزمایش اعتبارسنجی کاربران توسط اکانت گوگل آنها
اکنون که تنظیمات اکانت گوگل به پایان رسید و همچنین به برنامه نیز معرفی شد، برنامهها را اجرا کنید. مشاهده خواهید کرد که امکان لاگین توسط اکانت گوگل نیز به صورت خودکار به صفحهی لاگین IDP ما اضافه شدهاست:
در اینجا با کلیک بر روی دکمهی گوگل، به صفحهی لاگین آن که به همراه نام برنامهی ما است و انتخاب اکانتی از آن هدایت میشویم:
پس از آن، از طرف گوگل به صورت خودکار به IDP (همان آدرسی که در فیلد Authorized redirect URIs وارد کردیم)، هدایت شده و callback رخداده، ما را به سمت صفحهی ثبت اطلاعات کاربر جدید هدایت میکند. این تنظیمات را در قسمت قبل ایجاد کردیم:
namespace DNT.IDP.Controllers.Account { [SecurityHeaders] [AllowAnonymous] public class ExternalController : Controller { public async Task<IActionResult> Callback() { var result = await HttpContext.AuthenticateAsync(IdentityServer4.IdentityServerConstants.ExternalCookieAuthenticationScheme); var returnUrl = result.Properties.Items["returnUrl"] ?? "~/"; var (user, provider, providerUserId, claims) = await FindUserFromExternalProvider(result); if (user == null) { // user = AutoProvisionUser(provider, providerUserId, claims); var returnUrlAfterRegistration = Url.Action("Callback", new { returnUrl = returnUrl }); var continueWithUrl = Url.Action("RegisterUser", "UserRegistration" , new { returnUrl = returnUrlAfterRegistration, provider = provider, providerUserId = providerUserId }); return Redirect(continueWithUrl); }
در اینجا نحوهی اصلاح اکشن متد Callback را جهت هدایت یک کاربر جدید به صفحهی ثبت نام و تکمیل اطلاعات مورد نیاز IDP را مشاهده میکنید.
returnUrl ارسالی به اکشن متد RegisterUser، به همین اکشن متد جاری اشاره میکند. یعنی کاربر پس از تکمیل اطلاعات و اینبار نال نبودن user او، گردش کاری جاری را ادامه خواهد داد و به برنامه با این هویت جدید وارد میشود.
اتصال کاربر وارد شدهی از طریق یک IDP خارجی به اکانتی که هم اکنون در سطح IDP ما موجود است
تا اینجا اگر کاربری از طریق یک IDP خارجی به برنامه وارد شود، او را به صفحهی ثبت نام کاربر هدایت کرده و پس از دریافت اطلاعات او، اکانت خارجی او را به اکانتی جدید که در IDP خود ایجاد میکنیم، متصل خواهیم کرد. به همین جهت بار دومی که این کاربر به همین ترتیب وارد سایت میشود، دیگر صفحهی ثبت نام و تکمیل اطلاعات را مشاهده نمیکند. اما ممکن است کاربری که برای اولین بار از طریق یک IDP خارجی به سایت ما وارد شدهاست، هم اکنون دارای یک اکانت دیگری در سطح IDP ما باشد؛ در اینجا فقط اتصالی بین این دو صورت نگرفتهاست. بنابراین در این حالت بجای ایجاد یک اکانت جدید، بهتر است از همین اکانت موجود استفاده کرد و صرفا اتصال UserLogins او را تکمیل نمود.
به همین جهت ابتدا نیاز است لیست Claims بازگشتی از گوگل را بررسی کنیم:
var (user, provider, providerUserId, claims) = await FindUserFromExternalProvider(result); foreach (var claim in claims) { _logger.LogInformation($"External provider[{provider}] info-> claim:{claim.Type}, value:{claim.Value}"); }
External provider[Google] info-> claim:http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name, value:Vahid N. External provider[Google] info-> claim:http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname, value:Vahid External provider[Google] info-> claim:http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname, value:N. External provider[Google] info-> claim:urn:google:profile, value:https://plus.google.com/105013528531611201860 External provider[Google] info-> claim:http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress, value:my.name@gmail.com
[HttpGet] public async Task<IActionResult> Callback() { // ... var (user, provider, providerUserId, claims) = await FindUserFromExternalProvider(result); if (user == null) { // user wasn't found by provider, but maybe one exists with the same email address? if (provider == "Google") { // email claim from Google var email = claims.FirstOrDefault(c => c.Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"); if (email != null) { var userByEmail = await _usersService.GetUserByEmailAsync(email.Value); if (userByEmail != null) { // add Google as a provider for this user await _usersService.AddUserLoginAsync(userByEmail.SubjectId, provider, providerUserId); // redirect to ExternalLoginCallback var continueWithUrlAfterAddingUserLogin = Url.Action("Callback", new {returnUrl = returnUrl}); return Redirect(continueWithUrlAfterAddingUserLogin); } } } var returnUrlAfterRegistration = Url.Action("Callback", new {returnUrl = returnUrl}); var continueWithUrl = Url.Action("RegisterUser", "UserRegistration", new {returnUrl = returnUrlAfterRegistration, provider = provider, providerUserId = providerUserId}); return Redirect(continueWithUrl); }
کدهای کامل این قسمت را از اینجا میتوانید دریافت کنید.
برای اجرای برنامه:
- ابتدا به پوشهی 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 وارد کنید.
Top 3 Open Source ASP.NET Core based e-commerce web applications
nopCommerce
SimplCommerce
grandnode ( Powered By nopCommerce) :It’s an open source, free, cross-platform e-commerce software based on ASP.NET Core 2.2 and MongoDB — NoSQL database. You can run it on Linux, Windows, and MacOS easily. GrandNode also supports Docker, so you are able to install it just in seconds with Docker.
MyTested.AspNetCore.Mvc is a strongly-typed unit testing library providing an easy fluent interface to test the ASP.NET Core MVC framework. It is testing framework agnostic so that you can combine it with a test runner of your choice (e.g. xUnit, NUnit, etc.).
Docker lets you build and run apps, more quickly, with less hardware. That’s why application container technology (and Docker in particular) is so popular. You can run hundreds of containers on a server which could only run a handful of VMs, and containers are fast to deploy and can be versioned like the software they run.
خالق Null در زبانهای برنامه نویسی، آنرا یک اشتباه چند میلیارد دلاری میداند! و به عنوان یک توسعه دهندهی دات نت، غیرممکن است که در حین اجرای برنامههای خود تابحال به null reference exception برخورد نکرده باشید. هدف از ارائهی قابلیت جدید «نوعهای ارجاعی نالنپذیر» در C# 8.0، مقابلهی با یک چنین مشکلاتی است و خصوصا غنی سازی IDEها برای ارائهی اخطارهایی پیش از کامپایل برنامه، در مورد قسمتهایی از کد که ممکن است سبب بروز null reference exception شوند.
فعالسازی «نوعهای ارجاعی نالنپذیر»
قابلیت «نوعهای ارجاعی نالنپذیر» به صورت پیشفرض غیرفعال است. برای فعالسازی آن میتوان فایل csproj را به صورت زیر، با افزودن خاصیت NullableContextOptions، ویرایش کرد:
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>netcoreapp3.0</TargetFramework> <LangVersion>8.0</LangVersion> <NullableContextOptions>enable</NullableContextOptions> </PropertyGroup> </Project>
CS8632: The annotation for nullable reference types should only be used in code within a ‘#nullable’ context. CS8627: A nullable type parameter must be known to be a value-type or non-nullable reference type. Consider adding a ‘class’, ‘struct’ or type constraint.
<PropertyGroup> <LangVersion>preview</LangVersion> <Nullable>enable</Nullable> </PropertyGroup>
تغییر ماهیت نوعهای ارجاعی #C با فعالسازی NullableContextOptions
در #C ای که ما میشناسیم، رشتهها قابلیت پذیرش نال را دارند و همچنین ذکر آنها به صورت nullable بیمعنا است. اما پس از فعالسازی ویژگی نوعهای ارجاعی نالنپذیر، اکنون عکس آن رخ میدهد. رشتهها نالنپذیر میشوند؛ اما میتوان در صورت نیاز، آنها را nullable نیز تعریف کرد.
یک مثال: بررسی تاثیر فعالسازی NullableContextOptions بر روی یک پروژه
کلاس زیر را در نظر بگیرید:
public class Person { public string FirstName { get; set; } public string MiddleName { get; set; } public string LastName { get; set; } public Person(string first, string last) => (FirstName, LastName) = (first, last); public Person(string first, string middle, string last) => (FirstName, MiddleName, LastName) = (first, middle, last); public override string ToString() => $"{FirstName} {MiddleName} {LastName}"; }
در این کلاس، دو سازنده وجود دارند که یکی MiddleName را دریافت میکند و دیگری خیر. در اینجا کامپایلر تشخیص دادهاست که چون در سازندهی اولی که MiddleName را دریافت نمیکند، مقدار پیشفرض خاصیت MiddleName، نال خواهد بود و همچنین ما NullableContextOptions را نیز فعال کردهایم، بنابراین این خاصیت دیگر به صورت معمول و متداول یک نوع ارجاعی نالپذیر عمل نمیکند و دیگر نمیتوان نال را به عنوان مقدار پیشفرض آن، به آن نسبت داد. به همین جهت اخطار فوق ظاهر شدهاست.
برای رفع این مشکل:
به کامپایلر اعلام میکنیم: «میدانیم که MiddleName میتواند نال هم باشد» و آنرا در این زمینه راهنمایی میکنیم:
public string? MiddleName { get; set; }
public static class NullableReferenceTypes { //#nullable enable // Toggle to enable public static string Exemplify() { var vahid = new Person("Vahid", "N"); var length = GetLengthOfMiddleName(vahid); return $"{vahid.FirstName}'s middle name has {length} characters in it."; static int GetLengthOfMiddleName(Person person) { string middleName = person.MiddleName; return middleName.Length; } } }
در اینجا در متد محلی (local function) تعریف شده، سعی در دسترسی به خاصیت MiddleName وجود دارد و اکنون با تغییر جدیدی که اعمال کردیم، به صورت نالپذیر تعریف شدهاست.
همچنین در سطر بعدی آن نیز نتیجهی نهایی middleName، مورد استفاده قرار گرفتهاست که آن نیز مشکلدار تشخیص داده شدهاست.
مشکل اولین سطر را به این صورت میتوانیم برطرف کنیم:
var middleName = person.MiddleName;
اما مشکل سطر دوم هنوز باقی است:
علت اینجا است که متغیر middleName نیز اکنون ممکن است مقدار نال را داشته باشد. برای رفع این مشکل میتوان از اپراتور .? استفاده کرد و سپس اگر مقدار نهایی این عبارت نال بود، مقدار صفر را بازگشت میدهیم:
static int GetLengthOfMiddleName(Person person) { var middleName = person.MiddleName; return middleName?.Length ?? 0; }
امکان خاموش و روشن کردن ویژگی نوعهای ارجاعی نالنپذیر به صورت موضعی
زمانیکه خاصیت NullableContextOptions را فعال میکنیم، بر روی کل پروژه تاثیر میگذارد. برای مثال اگر یک چنین قابلیتی را بر روی پروژههای قدیمی خود فعال کنید، با صدها اخطار مواجه خواهید شد. به همین جهت است که این ویژگی حتی با فعالسازی C# 8.0 و انتخاب آن، به صورت پیشفرض غیرفعال است. بنابراین برای اینکه بتوان پروژههای قدیمی را قدم به قدم و سر فرصت، «مقاومتر» کرد، میتوان تعیین کرد که کدام قسمت، تحت تاثیر این ویژگی قرار بگیرد و کدام قسمتها خیر:
public static class NullableReferenceTypes { #nullable disable // Toggle to enable
مجبور ساختن خود به «مقاوم سازی» برنامه
اگر NullableContextOptions را فعال کنید، کامپایلر صرفا یکسری اخطار را در مورد مشکلات احتمالی صادر میکند؛ اما برنامه هنوز کامپایل میشود. برای اینکه خود را مقید به «مقاوم سازی» برنامه کنیم، میتوانیم با فعالسازی ویژگی TreatWarningsAsErrors در فایل csprj، این اخطارها را تبدیل به خطای کامپایلر کرده و از کامپایل برنامه جلوگیری کنیم:
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>netcoreapp3.0</TargetFramework> <LangVersion>8.0</LangVersion> <NullableContextOptions>enable</NullableContextOptions> <TreatWarningsAsErrors>true</TreatWarningsAsErrors> </PropertyGroup> </Project>
<WarningsAsErrors>CS8600;CS8602;CS8603</WarningsAsErrors>
آیا اگر برنامهای با C# 7.0 کامپایل شود، کتابخانههای تهیه شدهی با C# 8.0 را میتواند استفاده کند؟
پاسخ: بله. از دیدگاه برنامههای قدیمی، کتابخانههای تهیه شدهی با C# 8.0، تفاوتی با سایر کتابخانه ندارند. آنها نوعهای نالپذیر جدید را مانند ?string مشاهده نمیکنند؛ آنها فقط string را مشاهده میکنند و روش کار کردن با آنها نیز همانند قبل است. بدیهی است در این حالت از مزایای کامپایلر C# 8.0 در تشخیص زود هنگام مشکلات برنامه محروم خواهند بود؛ اما عملکرد برنامه تفاوتی نمیکند.
وضعیت برنامهی C# 8.0 ای که از کتابخانههای C# 7.0 و یا قبل از آن استفاده میکند، چگونه خواهد بود؟
چون کتابخانههای قدیمیتر از مزایای کامپایلر C# 8.0 استفاده نمیکنند، خروجیهای آن بدون بروز خطایی توسط کامپایلر C# 8.0 استفاده میشوند؛ چون حجم اخطارهای صادر شدهی در این حالت بیش از حد خواهد بود. یعنی این بررسیهای کامپایلر صرفا برای کتابخانههای جدید فعال هستند و نه برای کتابخانههای قدیمی.
مهارتهای مواجه شدن با اخطارهای ناشی از فعالسازی NullableContextOptions
در مثالی که بررسی شد، یک نمونه از روشهای مواجه شدن با اخطارهای ناشی از فعالسازی ویژگی نوعهای ارجاعی نالنپذیر را بررسی کردیم. در ادامه روشهای تکمیلی دیگری را بررسی میکنیم.
1- هرجائیکه قرار است متغیر ارجاعی نالپذیر باشد، آنرا صراحتا اعلام کنید.
string name = null; // ERROR string? name = null; // OK!
نمونهی دیگر آن مثال زیر است:
public class Person { public Address? Address { get; set; }; public string Country => Address?.Country; // ERROR! }
public class Person { public Address? Address { get; set; }; public string? Country => Address?.Country; // OK! }
البته در این حالت باید به مثال زیر دقت داشت:
var node = this; // Initialize non-nullable variable while (node != null) { node = null; // ERROR! }
Node? node = this; // Initialize nullable variable while (node != null) { node = null; // OK! }
2- نوعهای خود را مقدار دهی اولیه کنید.
در مثال زیر:
public class Person { public string Name { get; set; } // ERROR! }
public class Person { public string? Name { get; set; } }
یا یک استثناء را صادر کنید:
public class Person { public string Name { get; set; } public Person(string name) { Name = name ?? throw new ArgumentNullException(nameof(name)); } }
البته در این حالت برای مقدار دهی اولیهی Name، حتما نیاز به تعریف یک سازندهاست و در این حالت کدهایی را که از سازندهی پیشفرض استفاده کرده بودند (مانند new Person { Name = "Vahid" })، باید تغییر دهید.
راهحل دیگر، مقدار دهی اولیهی این خواص بدون تعریف یک سازندهی اضافی است:
public class Person { public string Name { get; set; } = string.Empty; // -or- public string Name { get; set; } = ""; }
String.Empty Array.Empty<T>() Enumerable.Empty<T>()
public class Person { public Address Address { get; set; } = new Address(); }
امکان ساخت قالب برای پروژههای NET Core.
dotnet new -i %~dp0
C:\Projects\DNTIdentity-master>dotnet new -i %~dp0 Restoring packages for C:\Users\Marjani\.templateengine\dotnetcli\v2.1.300\scratch\restore.csproj... C:\Users\Marjani\.templateengine\dotnetcli\v2.1.300\scratch\restore.csproj : error NU1101: Unable to find package %~dp0. No packages exist with this id in source(s): C:\Program Files\dotnet\sdk\NuGetFallbackFolder, Microsoft Visual Studio Offline Packages, nuget.org Generating MSBuild file C:\Users\Marjani\.templateengine\dotnetcli\v2.1.300\scratch\obj\restore.csproj.nuget.g.props. Generating MSBuild file C:\Users\Marjani\.templateengine\dotnetcli\v2.1.300\scratch\obj\restore.csproj.nuget.g.targets. Restore failed in 1.95 sec for C:\Users\Marjani\.templateengine\dotnetcli\v2.1.300\scratch\restore.csproj.