این مجموعه با نگاهی به ویدیوی آموزشی Best Practice در Pluralsight که توسط Steve Smith تهیه شده، گردآوری شده است. این مجموعه مقالات به شکل ویدیویی هم در کانال تلگرامم قرار میگیره. خوشحال میشم برای با خبر شدن از مقالات و نوشته ها و ویدیوهای آموزشی در این کانال حضور داشته باشید و نظراتتون رو بنویسید. https://t.me/mediapub_channel
فهرست مقالات مجموعه معرفی Best Practice های دات نت :
در این مقاله به مرور بهترین روش ها برای برقراری امنیت در یک پروژه دات نت می پردازیم.
در مورد JWT صحبت میکنیم و راه حل های جایگزین توکن را مطرح میکنیم. پیاده سازی authorization و authentication را انجام میدهیم و در مورد CORS هم صحبت میکنیم.
یکی از روش های رایج برای تامین امنیت 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 دسترسی داشته باشد تا توکن را وریفای کند.
به تصویر زیر نگاه کنید تا روش کار با پروتکلی مثل 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 میگیریم.
به کمک آنچه در TokenValidtionParameter به عنوان آپشن ها تعریف کرده ایم موارد فوق بررسی میشوند:
حمله کننده ها میتوانند از توکن جعلی استفاده کنند تا به 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 نمیکنید تا ولیدیشن همه جا به یک شکل انجام شود.
به بدنه خروجی لاگین نگاه کنید، یک access token داریم که برای دسترسی به ریسورسها از آن استفاده میکنیم و یک refresh token. بعد از مدت زمان expired مقدار access token قابل استفاده نخواهد بود و خطای 401 یا UnAuthorized دریافت میکنیم و باید دوباره لاگین کنیم.
اما رفرش توکن به درد همین موقع میخورد. کلاینت میتواند آن را در جایی نگه دارد و بوسیله آن و از طریق صدا زدن متد refresh مقدار Access token جدید را دریافت کند.
متد revoke نیز وجود دارد که میتوان یک refresh token را باطل کرد. در تصویر زیر میبینید که این متد برای یک refresh token صدا زده شده است و این رفرش توکن دیگر قابل استفاده نخواهد بود.
با revoke کردن دیگر رفرش توکن قابل استفاده نخواهد بود و کاربر حتما باید لاگین کند.
در تصاویر زیر متد RefreshToken و RevokeRefreshToken را میبینید.
اگر روی سیستم ویندوزی هستید از Windows Authentication میتوان استفاده کرد. این روش امنیت را در بسیاری از سناریوها ساده میکند و یک تنظیم حداقلی نیاز دارد.
روش دیگر Cookie based authentication است که برای روش هایی مثل ماژول های واسط بین فرانت و بکند قابل انجام است و به آن BFF هم میگویند.(مثلا پروژه های Blazor به کمک یک پروژه میانی Shared به منظور به اشتراک گذاری مدل ها میتوانند BFF خطاب شوند چون نوعی فراهم کننده دیتای مناسب برای فرانتند هستند). این روش را Token Handler هم میگویند که یعنی از توکن استفاده میکنند اما آن را به سمت مرورگر نمیفرستند.
میتوان چندین روش را برای یک نوع از Authentication هم تعریف کرد.
فراموش نکنید ترتیب اضافه کردم میدل ویرهای مربوط به اعتبار سنجی و هویت سنجی به شکل زیر است :
مفهوم Authentication : کاربر چه کسی است؟ آیا میشناسیم؟ آیا در سیستم ثبت شده؟
مفهوم Authorization : کاربر چه دسترسی هایی دارد؟ آیا مجوز دسترسی به api خاص را دارد؟
اگر بخواهیم مجوز دسترسی را بر اساس مقادیر ریسورس بدهیم باید Policy تعریف کنیم.
در مثال فوق EditPolicy یک پالیسی است که بر اساس کلاس SameAuthorRequirement کار میکند که این کلاس به شکل زیر باید پیاده سازی شود.
هندلری که از این کلاس استفاده میکند به شکل زیر است:
اگر api شما در مرورگر استفاده میشود باید CORS را تنظیم کنید. Cross origin resource sharing در استاندارد RFC 6454 تعریف شده است و در تعریف مرورگر ها مجبورند که در هر وب پیج امکان ریکوئست زدن به یک آدرس ناهمسان(با domain های مختلف) دیگر با آدرس جاری را کنترل کنند.
دو آدرس وقتی Origin یکسان دارند که host و port یکسان در آدرس آنها شناسایی شود. در این صورت مشکلی پیش نخواهد آمد. Cors به مرورگرها اجازه میدهد که یک پیج ریکوئست هایی به آدرسی ناهمسان با پیج بزند.
در حقیقت CORS یک پارامتر امنیتی نیست بلکه باعث میشود امنیت صفحه بیش از پیش فراهم شود و یک صفحه فقط بتواند با api های مرتبط با خودش در ارتباط باشد.
همچنین مجوز دسترسی باید در سمت سرور انجام شود و نه در جایی که استفاده میشود.پ
در تنظیمات یک یا چند آدرس را با یک نام پالیسی مشخص تفکیک و تعریف میکنیم. این آدرس دقیقا صفحه ایست که قرار است به api های ما دسترسی داشته باشد.
سپس به شکل زیر آن را به پایپلاین اضافه میکنیم.
به محل قرار گیری app.UseCors و ترتیب آن دقت کنید.
اگر به تعریف پالیسی های مختلف نیاز ندارد و تنها یک جا از سایت شما استفاده میکند به شکل زیر تنظیمات را انجام دهید.
پالیسی مربوط به CORS میتواند شامل سه بخش باشد.
میتوان به ازای enpoint ها هم کانفیگ CORS تعریف کرد:
به کمک دو اتریبیوت موجود در تصویر میتوان CORS خاصی را برای api فعال و غیرفعال کرد. در Minimal api هم به همین روش میتوان عمل کرد.
دات نت یک اتریبیوت به نام 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 هم نکاتی بیان شد.