در سیستمهای اتصال از راه دور به خصوص اتصال
تلفنهای همراه به وب سرویس، یکی از مواردی که مرتبا قبل از هر درخواستی
بررسی میگردد، صحت نام کاربری و کلمه عبور درخواستی است. در این روش کاربر،
الزامات امنیتی (نام کاربری و کلمه عبور) را در بدنه درخواست قرار داده و
هر api باید قبل از انجام عملیات، صحت آن را بررسی کند. این مورد باعث میشود
که کدها از حالت بهینه خارج شده و در سمت سرور، Api مربوطه باید صحت آن را
بررسی کند. در این مقاله قصد داریم با پیاده سازی Basic Authentication
این مشکل را رفع کرده تا به کد یکدستتری برسیم. Basic Authentication
طبق گفته ویکی پدیا در واقع یک روش در درخواستهای ارسالی http میباشد که
با فراهم سازی الزامات امنیتی، درخواست خود را به سرور ارائه میدهد. در این
روش نام کاربری و کلمه عبور به جای ارسال در بدنه درخواست، در هدر درخواست
قرار گرفته و الزامات امنیتی به صورت رمزگذاری شده (عموما Base64) به سمت
سرور ارسال میشوند.
این روش به دلیل عدم وجود ذخیره کوکی، ایجاد Session و دیگر مباحث امنیتی، جز سادهترین روشهای تشخیص هویت میباشد و همچنین مشخص است به دلیل رمزگذاری سادهای چون Base64 و همچنین در صورت ارسال از طریق http نه https، حفاظت چندانی در ارسال الزامات امنیتی ندارند. ولی عموما برای گوشیهای همراه تا حد زیادی قابل قبول است. همچنین در این روش مکانیزم logout فراهم نیست و تنها در طی یک درخواست پردازش اطلاعات انجام شده و بعد از آن هویت شما نابود خواهد شد.
برای ارسال اطلاعات در هدر درخواستی، به شیوه زیر اطلاعات را ارسال میکنیم:
از آنجا که گفتیم اطلاعات بالا بهتر هست در قالب خاصی رمزگذاری (encoding) شوند، خط بالا به صورت نهایی، به شکل زیر خواهد بود:
از آنجا که این کد اعتبارسنجی باید مرتبا بررسی شود، باعث میشود
که اصل تک مسئولیتی اکشن زیر سوال رفته و کدها در ابتدا مرتبا تکرار شوند.
این وظیفه را بر عهده Attribute ها
میگذاریم. در Attributeهای موجود در سیستم دات نت، میتوان به
AuthorizationFilterAttribute اشاره کرد که امکانات Authorization را در
اختیار ما قرار میدهد. کد زیر را برای آن وارد میکنیم:
در اولین خط از کد بررسی میشود که خاصیت Authorization در هدر درخواست وجود دارد یا خیر؛ در صورت که وجود نداشته باشد متد SayYouAreUnAuthorize صدا زده میشود. این متد وظیفه دارد، در صورتیکه صحت نام کاربری و کلمه عبور تایید نشد، خطای مورد نظر را که با کد 401 شناخته میشود، بازگرداند که در متد اصلی دو بار مورد استفاده قرار گرفته است. با استفاده از این بازگردانی و دادن پاسخ کاربر، دیگر api مورد نظر به مرحله اجرا نمیرسد.
در صورتیکه کد ادامه پیدا کند، مقدار تنظیم شده Authorization را دریافت کرده و در خط بعدی آن را از حالت انکود شده خارج میکنیم. از آنجاکه الگوی ارسالی ما به شکل username:password میباشد، با استفاده از متد الحاقی Split هر دو عبارت را جدا کرده و در خط بعد، صحت آن دو را مشخص میکنیم. از آنجا که این تکه کد تنها یک تست است، من آن را به شکل ساده و استاتیک نوشتهام. بدیهی است که در یک مثال واقعی، این تایید صحت توسط یک منبع داده انجام میشود. در صورتی که صحت آن تایید نشود مجددا متد SayYouAreUnAuthorize مورد استفاده قرار میگیرد. در غیر این صورت باید این تایید هویت را در ترد جاری تایید کرده و حفظ کنیم و حتی در صورت لزوم Id کاربر و نقشهای آن را ذخیره کنیم تا اگر api مورد نظر بر اساس آن تصمیم میگیرد، آنها را در اختیار داشته باشیم. به عنوان مثال ممکن است اطلاعات دریافتی پرداختیهای یک کاربر باشد که به UserId نیاز است.
از آنجا که در این روش نه ذخیره session و نه کوکی و نه مورد دیگری داریم و قصد داریم تنها در ترد یا درخواست جاری این اعتبار را نگه داریم، میتوانیم از thread.CurrentPrincipal استفاده کنیم. این خصوصیت نوع GenericPrincipal را نیاز دارد که دو پارامتر GenereicIdentity و آرایهای از نوع رشته را دریافت میکنید. این آرایهها متعلق به زمانی است که شما قصد دارید نقشهای یک کاربر را ارسال کنید و شیء GenericIdentity شیءایی است که شامل اطلاعات Authorization بوده و هیچ وابستگی به windows Domain ندارد. این شیء از شما تنها یک نام را به صورت رشته میگیرد که میتوانیم از طریق آن UserId را پاس کنیم. برای دریافت آن نیز داریم:
حال برای api به کد زیر نیاز داریم:
در api بالا attribute مدنظر بر روی آن اعمال شده و تنها مقادیر ورودی این api همان پارامترهای درخواست اصلی میباشد و ویژگی BasicAuthentication کدهای اعتبارسنجی را از کد اصلی دور نگه داشته است و api اصلی هیچگاه متوجه قضیه نمیشود و تنها کار اصلی خود را انجام میدهد. همچین در اینجا از طریق کلاس thread به UserId دسترسی داریم.
حال تنها لازم است کلاینت، قسمت هدر را پر کرده و آن را به سمت سرور ارسال کند. در اینجا من از نرم افزار I'm only resting استفاده میکنیم تا درخواست رسیده را در WebApi تست نمایم.
این روش به دلیل عدم وجود ذخیره کوکی، ایجاد Session و دیگر مباحث امنیتی، جز سادهترین روشهای تشخیص هویت میباشد و همچنین مشخص است به دلیل رمزگذاری سادهای چون Base64 و همچنین در صورت ارسال از طریق http نه https، حفاظت چندانی در ارسال الزامات امنیتی ندارند. ولی عموما برای گوشیهای همراه تا حد زیادی قابل قبول است. همچنین در این روش مکانیزم logout فراهم نیست و تنها در طی یک درخواست پردازش اطلاعات انجام شده و بعد از آن هویت شما نابود خواهد شد.
برای ارسال اطلاعات در هدر درخواستی، به شیوه زیر اطلاعات را ارسال میکنیم:
Authorization: Basic Username:Password
Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=
public class BasicAuthetication:AuthorizationFilterAttribute { public override void OnAuthorization(HttpActionContext actionContext) { if (actionContext.Request.Headers.Authorization == null) { SayYouAreUnAuthorize(actionContext); return; } var param = actionContext.Request.Headers.Authorization.Parameter; var decodedCridential = Encoding.UTF8.GetString(Convert.FromBase64String(param)); var splitedArray = decodedCridential.Split(':'); if (splitedArray[0] == "username" && splitedArray[1] == "password") { var userId = 1; var genericIdentity=new GenericIdentity(userId.ToString()); var genericPrincipal=new GenericPrincipal(genericIdentity,null); Thread.CurrentPrincipal = genericPrincipal; return; } SayYouAreUnAuthorize(actionContext); } private void SayYouAreUnAuthorize(HttpActionContext actionContext) { actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized); } }
در اولین خط از کد بررسی میشود که خاصیت Authorization در هدر درخواست وجود دارد یا خیر؛ در صورت که وجود نداشته باشد متد SayYouAreUnAuthorize صدا زده میشود. این متد وظیفه دارد، در صورتیکه صحت نام کاربری و کلمه عبور تایید نشد، خطای مورد نظر را که با کد 401 شناخته میشود، بازگرداند که در متد اصلی دو بار مورد استفاده قرار گرفته است. با استفاده از این بازگردانی و دادن پاسخ کاربر، دیگر api مورد نظر به مرحله اجرا نمیرسد.
در صورتیکه کد ادامه پیدا کند، مقدار تنظیم شده Authorization را دریافت کرده و در خط بعدی آن را از حالت انکود شده خارج میکنیم. از آنجاکه الگوی ارسالی ما به شکل username:password میباشد، با استفاده از متد الحاقی Split هر دو عبارت را جدا کرده و در خط بعد، صحت آن دو را مشخص میکنیم. از آنجا که این تکه کد تنها یک تست است، من آن را به شکل ساده و استاتیک نوشتهام. بدیهی است که در یک مثال واقعی، این تایید صحت توسط یک منبع داده انجام میشود. در صورتی که صحت آن تایید نشود مجددا متد SayYouAreUnAuthorize مورد استفاده قرار میگیرد. در غیر این صورت باید این تایید هویت را در ترد جاری تایید کرده و حفظ کنیم و حتی در صورت لزوم Id کاربر و نقشهای آن را ذخیره کنیم تا اگر api مورد نظر بر اساس آن تصمیم میگیرد، آنها را در اختیار داشته باشیم. به عنوان مثال ممکن است اطلاعات دریافتی پرداختیهای یک کاربر باشد که به UserId نیاز است.
از آنجا که در این روش نه ذخیره session و نه کوکی و نه مورد دیگری داریم و قصد داریم تنها در ترد یا درخواست جاری این اعتبار را نگه داریم، میتوانیم از thread.CurrentPrincipal استفاده کنیم. این خصوصیت نوع GenericPrincipal را نیاز دارد که دو پارامتر GenereicIdentity و آرایهای از نوع رشته را دریافت میکنید. این آرایهها متعلق به زمانی است که شما قصد دارید نقشهای یک کاربر را ارسال کنید و شیء GenericIdentity شیءایی است که شامل اطلاعات Authorization بوده و هیچ وابستگی به windows Domain ندارد. این شیء از شما تنها یک نام را به صورت رشته میگیرد که میتوانیم از طریق آن UserId را پاس کنیم. برای دریافت آن نیز داریم:
var userId=int.Parse(Thread.CurrentPrincipal.Identity.Name);
[BasicAuthetication] public HttpResponseMessage GetPayments(int type) { var userId = int.Parse(Thread.CurrentPrincipal.Identity.Name); if (type == 1) { return Request.CreateResponse(HttpStatusCode.OK, new { Id = 4, Price = 20000, userId=userId }); } return Request.CreateResponse(HttpStatusCode.OK, new { Id = 3, Price = 10000, userId = userId }); }
در api بالا attribute مدنظر بر روی آن اعمال شده و تنها مقادیر ورودی این api همان پارامترهای درخواست اصلی میباشد و ویژگی BasicAuthentication کدهای اعتبارسنجی را از کد اصلی دور نگه داشته است و api اصلی هیچگاه متوجه قضیه نمیشود و تنها کار اصلی خود را انجام میدهد. همچین در اینجا از طریق کلاس thread به UserId دسترسی داریم.
حال تنها لازم است کلاینت، قسمت هدر را پر کرده و آن را به سمت سرور ارسال کند. در اینجا من از نرم افزار I'm only resting استفاده میکنیم تا درخواست رسیده را در WebApi تست نمایم.