نظرات مطالب
اعتبارسنجی مبتنی بر JWT در ASP.NET Core 2.0 بدون استفاده از سیستم Identity
دو نوع پیاده سازی سمت کلاینت برای این مطلب تهیه شده؛ مثال صفحه‌ی HTML آن در پوشه‌ی wwwroot و همچنین مثال کامل و جامع Angular آن. مثال صفحه‌ی HTML آن یک مثال ساده‌است برای توضیح مفاهیم و فاقد تایمری برای فعالسازی خودکار refresh token؛ درحالیکه مثال Angular آن دارای تایمر مخصوصی برای اینکار است و پیش از رسیدن به زمان انقضای access token، یک توکن جدید را درخواست می‌کند. همچنین در مثال HTML آن توکن‌ها در local storage مرورگر ذخیره نمی‌شوند و با refresh صفحه، متغیرهای موقتی جاوا اسکریپتی که توکن‌ها را ذخیره کرده بودند، پاک خواهند شد. اما در مثال Angular آن بحث ذخیره سازی در local storage پیاده سازی شده‌است و خیلی جزئیات دیگر.
مطالب
امن سازی برنامه‌های ASP.NET Core توسط IdentityServer 4x - قسمت اول - نیاز به تامین کننده‌ی هویت مرکزی
عصر Thick Clients

امن سازی برنامه‌های وب همواره چالش برانگیز بوده‌است؛ خصوصا این روزها که نیاز است برنامه‌ها، خارج از دیوارهای یک شرکت نیز در دسترس باشند و توسط انواع و اقسام وسایل ارتباطی مورد استفاده قرار گیرند. در سال‌های قبل، عموما برنامه‌های 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 بهره‌مند شوید.
مطالب
روش استفاده‌ی از jQuery در برنامه‌های AngularJS 2.0
استفاده‌ی گسترده‌ی از jQuery به همراه برنامه‌های AngularJS 2.0 ایده‌ی خوبی نیست؛ زیرا jQuery و کدهای آن، ارتباط تنگاتنگی را بین DOM و JavaScript برقرار می‌کنند که برخلاف رویه‌ی فریم‌ورک‌های MVC است. اما با این حال استفاده‌ی از افزونه‌های بی‌شمار jQuery در برنامه‌های AngularJS 2.0، جهت غنا بخشیدن به ظاهر و همچنین کاربردپذیری برنامه، یکی از استفاده‌های پذیرفته شده‌ی آن است.
روش استفاده‌ی از jQuery نیز در حالت کلی همانند مطلب «استفاده از کتابخانه‌های ثالث جاوا اسکریپتی در برنامه‌های AngularJS 2.0» است؛ اما به همراه چند نکته‌ی اضافه‌تر مانند محل فراخوانی و دسترسی به DOM، در کدهای یک کامپوننت.


هدف: استفاده از کتابخانه‌ی Chosen

می‌خواهیم جهت غنی‌تر کردن ظاهر یک دراپ داون در برنامه‌های AngularJS 2.0، از یک افزونه‌ی بسیار معروف jQuery به نام Chosen استفاده کنیم.


تامین پیشنیازهای اولیه

می‌توان فایل‌های این کتابخانه را مستقیما از GitHub دریافت و به پروژه اضافه کرد. اما بهتر است این‌کار را توسط bower مدیریت کنیم. این کتابخانه هنوز دارای بسته‌ی رسمی npm نیست (و بسته‌ی chosen-npm که در مخزن npm وجود دارد، توسط این تیم ایجاد نشده‌است). اما همانطور که در مستندات آن نیز آمده‌است، توسط دستور ذیل نصب می‌شود:
bower install chosen
برای مدیریت این مساله در ویژوال استودیو، به منوی project و گزینه‌ی add new item مراجعه کرده و bower را جستجو کنید:


در اینجا نام پیش فرض bower.json را پذیرفته و سپس محتوای فایل ایجاد شده را به نحو ذیل تغییر دهید:
{
    "name": "asp-net-mvc5x-angular2x",
    "version": "1.0.0",
    "authors": [
        "DNT"
    ],
    "license": "MIT",
    "ignore": [
        "node_modules",
        "bower_components"
    ],
    "dependencies": {
        "chosen": "1.4.2"
    },
    "devDependencies": {
    }
}
ساختار این فایل هم بسیار شبیه‌است به ساختار package.json مربوط به npm. در اینجا کتابخانه‌ی chosen در قسمت لیست وابستگی‌ها اضافه شده‌است. با ذخیره کردن این فایل، کار دریافت خودکار بسته‌های تعریف شده، آغاز می‌شود و یا کلیک راست بر روی فایل bower.json دارای گزینه‌ی restore packages نیز هست.
پس از دریافت خودکار chosen، بسته‌ی آن‌را در مسیر bower_components\chosen واقع در ریشه‌ی پروژه می‌توانید مشاهده کنید.


استفاده از jQuery و chosen به صورت untyped

ساده‌ترین و متداول‌ترین روش استفاده از jQuery و افزونه‌های آن شامل موارد زیر هستند:
الف) تعریف مداخل مرتبط با آن‌ها در فایل index.html
<script src="~/node_modules/jquery/dist/jquery.min.js"></script>
<script src="~/node_modules/bootstrap/dist/js/bootstrap.min.js"></script>

<script src="~/bower_components/chosen/chosen.jquery.min.js"></script>
<link href="~/bower_components/chosen/chosen.min.css" rel="stylesheet" type="text/css" />
که در اینجا jQuery و بوت استرپ و chosen به همراه فایل css آن اضافه شده‌اند.
ب) تعریف jQuery به صورت untyped
declare var jQuery: any;
همینقدر که این یک سطر را به ابتدای ماژول خود اضافه کنید، امکان دسترسی به تمام امکانات جی‌کوئری را خواهید یافت. البته بدون intellisense و بررسی نوع‌های پارامترها؛ چون نوع آن، any یا همان حالت پیش فرض جاوا اسکریپت تعریف شده‌است.


محل صحیح فراخوانی متدهای مرتبط با jQuery

در تصویر ذیل، چرخه‌ی حیات یک کامپوننت را مشاهده می‌کنید که با تعدادی از آن‌ها پیشتر آشنا شده‌‌ایم:

روش متداول استفاده از jQuery، فراخوانی آن پس از رخداد document ready است. در اینجا معادل این رخداد، hook ویژ‌ه‌ای به نام ngAfterViewInit است. بنابراین تمام فراخوانی‌های jQuery را باید در این متد انجام داد.
همچنین جی‌کوئری نیاز دارد تا بداند هم اکنون قرار است با چه المانی کار کنیم و کامپوننت بارگذاری شده کدام است. برای این منظور، یکی از سرویس‌های توکار AngularJS 2.0 را به نام ElementRef، به سازنده‌ی کلاس تزریق می‌کنیم. توسط خاصیت this._el.nativeElement آن می‌توان به المان ریشه‌ی کامپوننت جاری دسترسی یافت.
constructor(private _el: ElementRef) {
}


تهیه‌ی کامپوننت نمایش یک دراپ داون مزین شده با chosen

در ادامه قصد داریم نکاتی را که تاکنون مرور کردیم، به صورت یک مثال پیاده سازی کنیم. به همین جهت فایل جدید using-jquery-addons.component.ts را به پروژه اضافه کنید به همراه فایل قالب آن به نام using-jquery-addons.component.html؛ با این محتوا:
الف) کامپوننت using-jquery-addons.component.ts
import { Component, ElementRef, AfterViewInit } from "@angular/core";

declare var jQuery: any; // untyped

@Component({
    templateUrl: "app/using-jquery-addons/using-jquery-addons.component.html"
})
export class UsingJQueryAddonsComponent implements AfterViewInit {

    dropDownItems = ["First", "Second", "Third"];
    selectedValue = "Second";

    constructor(private _el: ElementRef) {
    }

    ngAfterViewInit() {
        jQuery(this._el.nativeElement)
            .find("select")
            .chosen()
            .on("change", (e, args) => {
                this.selectedValue = args.selected;
            });
    }
}

ب) قالب using-jquery-addons.component.html
<select>
    <option *ngFor="let item of dropDownItems"
            [value]="item"
            [selected]="item == selectedValue">
        {{item}} option
    </option>
</select>

<h4> {{selectedValue}}</h4>
با این خروجی


توضیحات

کلاس UsingJQueryAddonsComponent، اینترفیس AfterViewInit را پیاده سازی کرده‌است؛ تا توسط متد ngAfterViewInit بتوانیم با عناصر DOM کار کنیم. هرچند در کل اینکار باید صرفا محدود شود به مواردی مانند مثال جاری و در حد آغاز یک افزونه‌ی jQuery و اگر قرار است تغییراتی دیگری صورت گیرند بهتر است از همان روش binding توکار AngularJS 2.0 استفاده کرد.
در سازنده‌ی کلاس، سرویس ElementRef تزریق شده‌است تا توسط خاصیت this._el.nativeElement آن بتوان به المان ریشه‌ی کامپوننت جاری دسترسی یافت. به همین جهت است که پس از آن از متد find، برای یافتن دراپ داون استفاده شده‌است و سپس chosen به نحو متداولی به آن اعمال گردیده‌است.
در اینجا هر زمانیکه یکی از آیتم‌های دراپ داون انتخاب شوند، مقدار آن به خاصیت selectedValue انتساب داده شده و این انتساب سبب فعال سازی binding و نمایش مقدار آن در ذیل دراپ داون می‌گردد.
در قالب این کامپوننت هم با استفاده از ngFor، عناصر دراپ داون از آرایه‌ی dropDownItems تعریف شده در کلاس کامپوننت، تامین می‌شوند. متغیر محلی item تعریف شده‌ی در اینجا، در محدودی همین المان قابل دسترسی است. برای مثال از آن جهت تنظیم دومین آیتم لیست به صورت انتخاب شده، در حین اولین بار نمایش view استفاده شده‌است.


استفاده از jQuery و chosen به صورت typed

کتابخانه‌ی jQuery در مخزن کد https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/jquery دارای فایل d.ts. خاص خود است. برای نصب آن می‌توان از روش ذیل استفاده کرد:
npm install -g typings
typings install jquery --save --ambient
پس از افزودن فایل typings این کتابخانه، در ابتدای فایل main.ts یک سطر ذیل را اضافه کنید:
 /// <reference path="../typings/browser/ambient/jquery/index.d.ts" />
اینکار سبب خواهد شد تا intellisense درون ویژوال استودیو بتواند مداخل متناظر را یافته و راهنمای مناسبی را ارائه دهد.

در ادامه به فایل using-jquery-addons.component.ts مراجعه کرده و تغییر ذیل را اعمال کنید:
// declare var jQuery: any; // untyped
declare var jQuery: JQueryStatic; // typed
فایل d.ts. اضافه شده، امکان استفاده‌ی از نوع جدید JQueryStatic را مهیا می‌کند. پس از آن در کدهای تعریف شده، chosen به رنگ قرمز در می‌آید، چون در تعاریف نوع دار jQuery، این افزونه وجود خارجی ندارد. برای رفع این مشکل، فایل typings/browser/ambient/jquery/index.d.ts را گشوده و سپس به انتهای آن، تعریف chosen را به صورت دستی اضافه کنید:
interface JQuery {
   //...
   chosen(options?:any):JQuery;
}
declare module "jquery" {
   export = $;
}
به این ترتیب این متد به عنوان جزئی از متدهای زنجیروار jQuery، شناسایی شده و قابل استفاده خواهد شد.


کدهای کامل این پروژه را از اینجا می‌توانید دریافت کنید.