معرفی Best Practice ها در Asp.Net Core : امنیت

این مجموعه با نگاهی به ویدیوی آموزشی Best Practice در Pluralsight که توسط Steve Smith تهیه شده، گردآوری شده است. این مجموعه مقالات به شکل ویدیویی هم در کانال تلگرامم قرار میگیره. خوشحال میشم برای با خبر شدن از مقالات و نوشته ها و ویدیوهای آموزشی در این کانال حضور داشته باشید و نظراتتون رو بنویسید. https://t.me/mediapub_channelفهرست مقالات مجموعه معرفی Best Practice های دات نت :مقدمه : آشنایی با REST و پیاده سازی GET/POST/PUT/DELETEبخش اول : پیاده سازی Responseبخش دوم : مدل های apiبخش سوم : پیاده سازی api بخش چهارم : امنیت (همین مقاله)در این مقاله به مرور بهترین روش ها برای برقراری امنیت در یک پروژه دات نت می پردازیم.در مورد JWT صحبت میکنیم و راه حل های جایگزین توکن را مطرح میکنیم. پیاده سازی authorization و authentication را انجام میدهیم و در مورد CORS هم صحبت میکنیم.استفاده از JWTیکی از روش های رایج برای تامین امنیت api ها استفاده از jwt است. jwt سه بخش دارد که در تصویر زیر مشخص است که با نقطه از هم جدا شده اند :هر بخش از نوع Base64 است. بخش اول header است. بخش دوم payload است. بخش سوم signature است. نکته مهم در مورد jwt این است که اطلاعات encode شده اند و encrypt نشده اند. پس هرگز اطلاعات حساس و مهم را در jwt نگهداری نکنید.اگر قرار است خودتان JWT خودتان را درست کنید نیاز دارید که 256 بیت باشد. اگر از نوع استرینگ است طول 64 هگزادسیمال کاراکتری داشته باشد یا حداقل 43 کاراکتر حرف بزرگ و کوچک و عدد داشته باشد تا به هم ریختگی کافی برای غیرقابل نفوذ بودن را دارا باشد.کلمه secret که برای verify کردن signature استفاده میشود باید بین STS (secure toke server) و Api Server که توکن را وریفای میکند شر باشد. اگر از پروتکلی مثل OAuth استفاده میکنید سرور web api نیاز دارد که به STS دسترسی داشته باشد تا توکن را وریفای کند.فراموش نکنید رمز ها و اطلاعات مهم را در JWT ذخیره نکنید.به تصویر زیر نگاه کنید تا روش کار با پروتکلی مثل OAuth2 را متوجه شوید.کلاینت درخواست لاگین را به سرور یا سرویس مربوط به توکن ارسال میکند.با فرض اینکه این درخواست درست باشد و کاربر اعتبارسنجی شود پیغام موفقیت در پاسخ ارسال میشود.کاربر در یک api دیگر درخواست توکن ارسال میکند.در پاسخ به کاربر access token که شامل claim هاست ارسال میشود.کلاینت این access token را کش میکند.(برای ریکوئست های بعدی)در ریکوئست های بعدی این توکن به شکل هدر Authorization به سمت سرور ارسال میشود و توسط سرور درستی آن بررسی شده و اجازه دسترسی به کاربر داده میشود. در صورت عدم دسترسی به دلیل valid نبودن توکن پیغام 401 صادر میشود(UnAuthorized). درصورتی که توکن ولید باشد اما سطح دسترسی لازم توسط کاربر وجود نداشته باشد خطای 403 برگردانده میشود (Forbidden) . راه حل دیگر صدور پیغام 404 توسط سرور است که میتواند برای جلوگیری از نمایش جزئیات خطا به دلایل امنیتی مفید باشد.مثلا میخواهید به یک ریپازیتوری خصوصی گیتهاب دسترسی داشته باشید با اینکه login هستید اما خطای 404 میگیرید چون دسترسی به ریپازیتوری خصوصی ندارید.در asp.net core برای کار کردن با jwt چندین پکیج وجود دارد.مثلا Microsoft.AspNetCore.Authentication.JwtBearer که یک middleware برای دریافت و اعتبارسنجی توکن هاست.مثلا System.IdentityModel.Tokens.Jwt کتابخانه دیگریست که برای ایجاد و سریالایز کردن و ولیدیت کردن JWT استفاده میشود.به جز این دو پکیج های دیگری هم وجود دارند.برای حفاظت از api ها به شکل زیر عمل میکنیم. دو endpoint ساده در کنترلر زیر داریمهر دوی این endpoint ها اتریبیوت authorize دارند. در دومی Role نیز مشخص شده است. اگر بخواهیم بدون استفاده از توکن به این endpoint ها دسترسی داشته باشیم خطای 401 میگیریم.برای راه اندازی پروژه یک اکستنشن متد مینویسیم که jwt را معرفی کنیم. تنظیمات مربوط به jwt در اینجا تعریف شده و سرویس آن به کمک کد زیر به application اضافه میشود.در این تنظیمات مشخص کرده ایم که توکن چطور ولیدیت شود.یک متد لاگین داریم که اگر موفقیت آمیز صدا زده شود یک توکن به کلاینت برمیگرداند. هندلر لاگین حاوی متدهای زیر است:در خط 66 تا 74 آبجکتی میسازیم که محتوای توکن را مشخص میسازد:پراپرتی issuer : صادر کنندهپراپرتی audience : مخاطبپراپرتی calims : اطلاعات اضافی(توسط متد GetClaims درست میشود)پراپرتی expires : زمان انقضاپراپرتی notBefore : زمان ایجادپراپرتی signingCredentials : نحوه انکریپشنمتد لاگین بر اساس موجود بودن نام کاربری و کلمه عبور یک توکن تولید میکند.در حقیقت مرحله Authentication باید توسط خود ما انجام شود یعنی نام کاربری و کلمه عبور را در دیتابیس سرچ میکنیم.مرحله Authorization توسط Jwt انجام میشود. یعنی توکنی در اختیار ما قرار میدهد که محتویاتی دارد و توسط آن توکن و با قراردادن آن در هدر ریکوئست ها میتوانیم دسترسی کاربر به api ها را مدیریت کنیم.کافیست توکن را به عنوان Bearer Token در ریکوئست های دیگر به شکل فوق وارد کنیم.درصورتی که توکن معتبر باشد و منقضی نشده باشد دسترسی به api های دیگر ممکن است:در صورتی که توکن برای یک api با Role مشخصی که مربوط به این توکن نیست استفاده شود خطای زیر را میبینیم :این یعنی اپلیکیشن ما را میشناسد ولی دسترسی به این بخش را نداریم.اگر توکن باطل شود خطای 401 میگیریم.سه بخش مربوط به این توکن توسط سایت jwt.io قابل رویت است.مواردی که در استفاده از JWT باید در نظر گرفتاز آخرین ورژن jwt استفاده کنید.قبل از استفاده حتما توکن را ولیدیت کنید(کاری که خودکار توسط اتریبیوت Authorize در دات نت انجام میشود)عمر توکن را ولیدیت کنید که باطل نشده باشد.بخش audience را چک کنید که توکن مربوط به api شما باشد.به کمک آنچه در TokenValidtionParameter به عنوان آپشن ها تعریف کرده ایم موارد فوق بررسی میشوند:مثلا فیلد aud مربوط به audience است که ولید بودن آن چک میشود، لایف تایم نیز true است پس ولید بودن آن چک میشود و بر اساس nbf و exp است.(not before و expired date). iss همان فیلد issuer است که طبق کانفیگ گفته شده ولید بودن آن چک شود.حمله کننده ها میتوانند از توکن جعلی استفاده کنند تا به api شما دسترسی پیدا کنند، مطمئن باشید که توکن expired نشده باشد و بعد از زمان nbf (not before) باشد. iss را چک کنید که توسط issuer مطمئن تولید شده باشد. توسط aud هم چک کنید که برای شما باشد.اگر از سرتیفیکیت به جای secret key استفاده میکنید Url ها مربوط به سرتیفیکت را محدود کنید در غیر اینصورت کاربران خرابکار از توکنی که با سرتیفیکیت خودشان درست شده با هر URL دیگری استفاده میکنند و ممکن است شما آنها را ولید کنید.از secret key قوی استفاده کنید. اگر خرابکارها به سکرت شما دست پیدا کنند میتوانند توکن های شما را دستکاری کنند و تاییده از شما بگیرند.کتابخانه jwt-cracker میتواند فقط ظرف کمتر از 2 ساعت به کمک یک لپتاپ secret key را پیدا کند! سکرت خود را به شکل alphanumeric و با طول بیش از 43 کاراکتر بسازید.از روش Asymetric key برای ولیدیشن یک توکن بین چند سرور استفاده کنید. مثل OpenId که از همین روش استفاده میکند.مثلا در یک سرویسی که از google account استفاده میکند sign in میکنید. گوگل از Private key برای تایید توکن استفاده میکند سپس اپلیکیشن از public key مربوط به گوگل برای تایید توکن استفاده میکند. فایده اصلی این روش این است که شما بین چند سرویس Secret Key را share نمیکنید تا ولیدیشن همه جا به یک شکل انجام شود.استفاده از jwt و refresh tokenبه بدنه خروجی لاگین نگاه کنید، یک access token داریم که برای دسترسی به ریسورسها از آن استفاده میکنیم و یک refresh token. بعد از مدت زمان expired مقدار access token قابل استفاده نخواهد بود و خطای 401 یا UnAuthorized دریافت میکنیم و باید دوباره لاگین کنیم.اما رفرش توکن به درد همین موقع میخورد. کلاینت میتواند آن را در جایی نگه دارد و بوسیله آن و از طریق صدا زدن متد refresh مقدار Access token جدید را دریافت کند.متد revoke نیز وجود دارد که میتوان یک refresh token را باطل کرد. در تصویر زیر میبینید که این متد برای یک refresh token صدا زده شده است و این رفرش توکن دیگر قابل استفاده نخواهد بود.با revoke کردن دیگر رفرش توکن قابل استفاده نخواهد بود و کاربر حتما باید لاگین کند. در تصاویر زیر متد RefreshToken و RevokeRefreshToken را میبینید.رفرش توکن ابتدا با متد TakeRefreshToken رفرش توکن را پاک میکند چون با صدا زدن این متد استفاده شده محسوب میشود. رفرش توکن جدید را به نام token بر میگرداند. در ادامه با کمک ایمیل کاربر را پیدا کرده و accessToken ساخته میشود. متد RevokeRefreshToken هم رفرش توکن جاری را پاک میکند.روش های جایگزین برای Authenticationاگر روی سیستم ویندوزی هستید از Windows Authentication میتوان استفاده کرد. این روش امنیت را در بسیاری از سناریوها ساده میکند و یک تنظیم حداقلی نیاز دارد.روش دیگر Cookie based authentication است که برای روش هایی مثل ماژول های واسط بین فرانت و بکند قابل انجام است و به آن BFF هم میگویند.(مثلا پروژه های Blazor به کمک یک پروژه میانی Shared به منظور به اشتراک گذاری مدل ها میتوانند BFF خطاب شوند چون نوعی فراهم کننده دیتای مناسب برای فرانتند هستند). این روش را Token Handler هم میگویند که یعنی از توکن استفاده میکنند اما آن را به سمت مرورگر نمیفرستند. استفاده از پکیج Negotiate برای اضافه کردن اعتبارسنجی ویندوی به پروژهبعد از مرحله 1 و 2 در مرحله 3 توکن به سرور BFF ارسال میشود و این سرور آن را برای استفاده های بعدی نگه میدارد و کوکی را به سمت کلاینت میفرستند. کلاینت به کمک این کوکی به شکل اتوماتیک درخواست های بعدی را با کوکی هندل میکند و ریکوئست 5 را با کوکی میفرستد. حالا سرور BFF با توجه به کوکی، توکن مناسب را برای Api Call ارسال میکند.تعریف بیش از یک روش برای Authenticationبه کمک اتریبیوت میتوان مشخص کرد که از کدام روش میخواهیم برای authorize استفاده کنیم. در این روش هم کوکی و هم jwt تعریف شده است.میتوان چندین روش را برای یک نوع از Authentication هم تعریف کرد.اضافه کردن Middleware فراموش نکنید ترتیب اضافه کردم میدل ویرهای مربوط به اعتبار سنجی و هویت سنجی به شکل زیر است :مفهوم Authentication : کاربر چه کسی است؟ آیا میشناسیم؟ آیا در سیستم ثبت شده؟مفهوم Authorization : کاربر چه دسترسی هایی دارد؟ آیا مجوز دسترسی به api خاص را دارد؟مفهوم Resource-Based Authorizationاگر بخواهیم مجوز دسترسی را بر اساس مقادیر ریسورس بدهیم باید Policy تعریف کنیم.در مثال فوق EditPolicy یک پالیسی است که بر اساس کلاس SameAuthorRequirement کار میکند که این کلاس به شکل زیر باید پیاده سازی شود.هندلری که از این کلاس استفاده میکند به شکل زیر است:مفهوم CORSاگر api شما در مرورگر استفاده میشود باید CORS را تنظیم کنید. Cross origin resource sharing در استاندارد RFC 6454 تعریف شده است و در تعریف مرورگر ها مجبورند که در هر وب پیج امکان ریکوئست زدن به یک آدرس ناهمسان(با domain های مختلف) دیگر با آدرس جاری را کنترل کنند. دو آدرس وقتی Origin یکسان دارند که host و port یکسان در آدرس آنها شناسایی شود. در این صورت مشکلی پیش نخواهد آمد. Cors به مرورگرها اجازه میدهد که یک پیج ریکوئست هایی به آدرسی ناهمسان با پیج بزند.در حقیقت CORS یک پارامتر امنیتی نیست بلکه باعث میشود امنیت صفحه بیش از پیش فراهم شود و یک صفحه فقط بتواند با api های مرتبط با خودش در ارتباط باشد.همچنین مجوز دسترسی باید در سمت سرور انجام شود و نه در جایی که استفاده میشود.پتنظیمات CORS در فایل Program.csدر تنظیمات یک یا چند آدرس را با یک نام پالیسی مشخص تفکیک و تعریف میکنیم. این آدرس دقیقا صفحه ایست که قرار است به api های ما دسترسی داشته باشد.سپس به شکل زیر آن را به پایپلاین اضافه میکنیم.به محل قرار گیری app.UseCors و ترتیب آن دقت کنید.اگر به تعریف پالیسی های مختلف نیاز ندارد و تنها یک جا از سایت شما استفاده میکند به شکل زیر تنظیمات را انجام دهید.پالیسی مربوط به CORS میتواند شامل سه بخش باشد.میتوان به ازای enpoint ها هم کانفیگ CORS تعریف کرد:به کمک دو اتریبیوت موجود در تصویر میتوان CORS خاصی را برای api فعال و غیرفعال کرد. در Minimal api هم به همین روش میتوان عمل کرد.استفاده از HTTPدات نت یک اتریبیوت به نام RequiredHttps دارد که برای ریکوئست های مربوط به مرورگر طراحی شده و برای APi نیست.میتوانید از کد 302 برای صدا زدن api هایتان با HTTP استفاده کنید (که یعنی باید به شکل HTTPS صدا زده شوند)بهتر است همه Http ها را ریجکت کنید و 400 برگردانید. در حقیقت پیشنهاد میشود فقط HTTPS را accept کنید و api های اپلیکیشن خود را در یک reverse proxy قرار دهید و فقط ریکوئست هایی که HTTPS را پشتیبانی میکنند فوروارد کنید.میتواند اپلیکیشن خود را جوری تنظیم کنید که فقط به ریکوئست های HTTPS گوش کند.یکی از راه ها استفاده از متد UseUrls در program.cs است.میتوانید برای اینکار و مشخص کردن startup url ها از environment variable ها استفاده کنید که در تصویر فوق مشخص هستند. این روش زمانی که با پابلیش به وسیله داکر ترکیب میشود بسیار ساده و سودمند است. جمع بندیدر این بخش در مورد مقدمات JWT آموختیم که چطور به طور مناسب از آن استفاده کنیم. همچنین در مورد روش های دیگر Authentication صحبت شد.در نهایت در مورد CORS و HTTPS هم نکاتی بیان شد.