حسین احمدی
بنیانگذار توسینسو و برنامه نویس و توسعه دهنده ارشد وب

آموزش کنترل دسترسی کاربران در دات نت با Principal و Identity

یکی از مباحث بسیار مهم در دات نت مبحث امنیت (Security) است. زمانی که شما قصد توسعه یک نرم افزار را دارید علاوه بر پیاده سازی کلیات منطق برنامه، می بایست به نحوی برنامه را پیاده سازی کنید که امنیت اطلاعات برنامه نیز تضمین شود. این تضمین شامل کنترل دسترسی کاربران به بخش های مختلف برنامه و همچنین ذخیره سازی اطلاعات مهم به صورت رمزنگاری شده در بخش های ذخیره سازی اطلاعات مانند بانک اطلاعاتی است. در این مطلب به مورد اول یعنی کنترل دسترسی کاربرها به برنامه خواهیم پرداخت. در ابتدا با دو مفهوم اصلی در امنیت یعنی Authentication و Authorization آشنا می شویم :

دوره های شبکه، برنامه نویسی، مجازی سازی، امنیت، نفوذ و ... با برترین های ایران
  1. Authentication : پروسه ای که کاربر در آن شناسایی می شود تا مجوز های دسترسی به اون تخصیص داده شود Authentication نام دارد. برای مثال، زمانی که شما قصد استفاده از وب سایت توسینسو را دارید، برای استفاده از برخی امکانات سایت باید با نام کاربری و کلمه عبور خود به سایت وارد شوید. بعد از وارد کردن نام کاربری و کلمه عبور هویت شما در وب سایت تائید شده و اجازه دسترسی به برخی بخش های سایت را خواهید داشت. پروسه تائید هویت به صورت های گوناگونی امکان پذیر است، مانند وارد کردن نام کاربری و کلمه عبور، اسکن عنبیه چشم، اسکن صورت و ... که همگی بر اساس یکسری پارامتر هویت کاربر را بررسی و تصدیق می کنند.
  2. Authorization : پس از تصدیق هویت کاربر یکسری مجوز ها برای او صادر می شود. مجدد وب سایت ITPRO را مثال می زنیم، زمانی که شما پروسه Authentication یا تائید هویت را پشت سر گذاشتید، مجوزهای مربوطه برای شما صادر می شود، برای مثال، امکان ارسال پست، ارسال مقاله و ... برای شما فعال می شود، اما برای مثال، شما در وب سایت امکان ویرایش مشخصات پروفایل سایر کاربران را نخواهید داشت و تنها می توانید اطلاعات پروفایل خود را ویرایش کنید، پروسه Authentication مشخص می کند که کاربر تائید هویت شده چه دسترسی هایی خواهد داشت.

در برنامه های دات نت نیز می توان پروسه های Authentication و Authorization را جهت کنترل دسترسی کاربران پیاده سازی کرد. برای این کار نیاز به آشنایی با مفاهیم Identity و Principal در دات نت داریم. زمانی که در برنامه های دات نت یک کاربر به سیستم وارد می شود بوسیله Identity اطلاعات آن کاربر را نگهداری می کنیم و در کنار آن، دسترسی های کاربر را بوسیله Principal مشخص می کنیم. دو اینترفیس IIdentity و IPrincipal در دات نت برای اینکار وجود دارند و البته کلاس هایی هم هستند که این اینترفیس را پیاده سازی کرده اند. این کلاس ها و اینترفیس ها در فضای نام System.Security.Principal قرار دارند:

  1. کلاس های WindowsIdentity و WindowsPrincipal: این کلاس ها حاوی اطلاعات کاربر ویندوز هستند، زمانی که یک کاربر به ونیدوز لاگین می کند، اطلاعات کاربری او در ویندوز بواسطه این دو کلاس قابل دسترس است.
  2. کلاس های GenericIdentity و GenericPrincipal: در صورتی که بخواهید پروسه Authentication و Authorization را جدا از حساب کاربری ویندوز پیاده سازی کنید می توانید از این دو کلاس استفاده کنید.

علاوه بر کلاس های بالا، از نسخه 4.5 دات قابلیت جدیدی اضافه شده به نام Claims که می توان به صورت جدیدی Identity ها و Principal ها را استفاده کرد که در دات نت اصطلاحاً به آن WIF یا Windows Identity Foundation گفته می شود.

کلاس های WindowsIdentity و WindowsPrincipal

برای شروع ابتدا با کلاس های WindowsIdentity و WindowsPrincipal آشنا شده و اینکه چگونه برنامه خود را با حساب های کاربری ویندوز یکپارچه کنیم. برای بدست آوردن کاربر جاری ویندوز کافیست به صورت زیر عمل کنیم:

var identity = WindowsIdentity.GetCurrent();
Console.WriteLine(identity.Name);

خروجی دستورات بالا نام حساب کاربری ویندوز خواهد که بر روی سیستم من عبارت زیر نمایش داده می شود:

DESKTOP-CRF890N\Hossein Ahmadi

بعد از بدست آوردن حساب کاربری ویندوز از روش بالا، کلاس Principal را به صورت ایجاد کرده و استفاده می کنیم:

var identity = WindowsIdentity.GetCurrent();
Console.WriteLine(identity.Name);

var principal = new WindowsPrincipal(identity);
Console.WriteLine(principal.IsInRole("BuiltIn\\Users"));

همانطور که در کد بالا مشاهده می کنید برای ایجاد کلاس Principal به عنوان پارامتر سازنده کلاس WindowsPrincipal، شئ identity استفاده شده است. با اینکار و بوسیله کلاس WindowsIdentity می توانیم بررسی کنیم که یک کاربر دسترسی های مورد نظر را دارد یا خیر، برای نمونه در کد بالا بوسیله متد IsInRole بررسی کردیم که کاربر جاری عضو گروه Users در سیستم Local هست یا خیر. به عنوان مثال بعدی، در کد زیر لیست کلیه گروه هایی که کاربر در آن ها عضو می باشد را در خروجی نمایش می دهیم:

foreach(var group in identity.Groups)
{
    Console.WriteLine(group.Value);
}

با اجرای کد بالا خروجی نمایش داده شده حاوی یکسری کدها و اعداد نامفهوم است:

S-1-5-21-2167674382-1402212879-1573101476-513
S-1-1-0
S-1-5-114
S-1-5-21-2167674382-1402212879-1573101476-1002
S-1-5-32-544
S-1-5-32-562
S-1-5-32-578
S-1-5-32-559
S-1-5-32-545
S-1-5-4
S-1-2-1
S-1-5-11
S-1-5-15
S-1-5-113
S-1-2-0
S-1-5-64-10
S-1-5-32-4028125388-2803578072-1053907958-341417128-2434011155-477421480-740873757-3973419746
S-1-5-32-2745667521-2937320506-1424439867-4164262144-2333007343-2599685697-2993844191-2003921822
S-1-5-32-1034403361-4122601751-838272506-684212390-1217345422-475792769-1698384238-1075311541

در حقیقت خروجی که ما دریافت کردیم شامل شناسه گروه های موجود در سیستم می باشد. برای دریافت اسامی گروه ها کافیست کد بالا را به صورت زیر تغییر دهیم:

foreach(var group in identity.Groups)
{
    Console.WriteLine(group.Translate(typeof(NTAccount)));
}

بوسیله کد بالا و بوسیله متد Translate گروه مورد نظر را به کلاس NTAccount ترجمه کردیم و با اینکار به جای دریافت شناسه گروه ها نام گروه ها به صورت زیر در خروجی نمایش داده می شود:

Everyone
NT AUTHORITY\Local account and member of Administrators group
DESKTOP-CRF890N\HelpLibraryUpdaters
BUILTIN\Administrators
BUILTIN\Distributed COM Users
BUILTIN\Hyper-V Administrators
BUILTIN\Performance Log Users
BUILTIN\Users
NT AUTHORITY\INTERACTIVE
CONSOLE LOGON
NT AUTHORITY\Authenticated Users
NT AUTHORITY\This Organization
NT AUTHORITY\Local account
LOCAL
NT AUTHORITY\NTLM Authentication

متد Translate در موارد دیگری نیز کاربر دارد، برای مثال، در ابتدای مقاله کد زیر را برای بدست آوردن اطلاعات حساب کاربر جاری ویندوز استفاده کردیم:

var identity = WindowsIdentity.GetCurrent();
Console.WriteLine(identity.Name);

بوسیله کد زیر می توان شناسه حساب کاربری جاری را در سیستم بدست آورد:

var identity = WindowsIdentity.GetCurrent();
Console.WriteLine(identity.Name);
var account = new NTAccount(identity.Name);
Console.WriteLine(account.Translate(typeof(SecurityIdentifier)));

خروجی دستورات بالا به صورت زیر خواهد بود:

DESKTOP-CRF890N\Hossein Ahmadi
S-1-5-21-2167674382-1402212879-1573101476-1001

برای پارامتر ورودی متد Translate می توان از دو کلاس SecurityIdentifier و NTAccount استفاده کرد که کلاس اولی برای بدست آوردن Identitfer گروه یا کاربر استفاده شده و کلاس دوم برعکس مورد اولی است که کاربرد هر دوی آن ها را در کد های بالا مشاهده کردید.همانطور که گفتیم از متد IsInRole برای بررسی دسترسی یک کاربر استفاده می شود. علاوه بر اینکه می توان نام دسترسی را به صورت مستقیم به متد IsInRole در کلاس WindowsIdentity ارسال کرد، امکان ارسال دسترسی مورد نظر به صورت کلاس SecurityIdentifier نیز وجود دارد. برای مثال، در کد زیر ما از کلاس SecurityIdentifier و همچنین enum ای با نام WellKnownSidType برای بررسی دسترسی مورد نظر استفاده کردیم:

var identity = WindowsIdentity.GetCurrent();
Console.WriteLine(identity.Name);
var principal = new WindowsPrincipal(identity);
var sid = new SecurityIdentifier(WellKnownSidType.BuiltinUsersSid, null);
Console.WriteLine(principal.IsInRole(sid));

بوسیله کد بالا همان دسترسی BuiltinUsers را بوسیله کلاس SecurityIdentifier بررسی کردیم. در WellKnownSidType آیتم های دیگری نیز وجود دارد که می توان از آن ها برای بررسی دسترسی استفاده کرد. همانطور که در کد بالا مشاهده کردید، برای پارامتر دوم کلاس SecurityIdentifier مقدار null را استفاده کردیم. برای بررسی دسترسی در سطح domain می توان از از پارامتر دوم استفاده کرد. برای مثال، در کد زیر بررسی می کنیم که کاربر در سطح domain دسترسی مدیریتی دارد یا خیر:

var identity = WindowsIdentity.GetCurrent();
Console.WriteLine(identity.Name);
var principal = new WindowsPrincipal(identity);
var account = identity.User.AccountDomainSid.Translate(typeof(NTAccount));
var sid = new SecurityIdentifier(WellKnownSidType.AccountDomainAdminsSid, identity.User.AccountDomainSid);
Console.WriteLine(principal.IsInRole(sid));

کلاس های GenericIdentity و GenericPrincipal

تا این لحظه با نحوه استفاده از کلاس های WindowsIdentity و WindowsPrincipal آشنا شدیم. اما اگر بخواهیم روند Authentication و Authorization را بر اساس حساب های کاربری که در خود برنامه تعریف شده پیاده سازی کنیم می بایست از کلاس های GenericIdentity و GenericPrincipal استفاده کنیم. استفاده از این کلاس ها خیلی ساده است، در نمونه کد زیر با فرض اینکه کاربری با نام Hossein از برنامه استفاده می کند و دسترسی های UsersAdmin و ProductsAdmin را دارد از کلاس های ذکر شده استفاده می کنیم:

var username = "Hossein";
var identity = new GenericIdentity(username);

var roles = new[] { "UsersAdmin", "ProductsAdmin" };

var principal = new GenericPrincipal(identity, roles);

همانطور که مشاهده می کنید نام کاربری به عنوان پارامتر به کلاس GenericIdentity ارسال شده و شئ identity و متغیر roles نیز به کلاس GenericPrincipal ارسال شده اند. فراموش نکنید که مقادیر username و roles می بایست از بانک اطلاعاتی خوانده شوند و بعد از تائید هویت کاربر کلاس های بالا را ایجاد کنید. بعد از انجام کارهای بالا، در قدم بعدی می بایست Principal ایجاد شده را برای Thread جاری به صورت زیر ست کنید:

Thread.CurrentPrincipal = principal;

بعد از ست کردن Principal می توانیم روند بررسی دسترسی ها را به صورت زیر انجام دهیم:

public static void CheckPrincipal()
{
    if (Thread.CurrentPrincipal.IsInRole("UsersAdmin"))
    {
        Console.WriteLine("welcome to Users Admin section.");
    }
    else
    {
        Console.WriteLine("You don't have permission to access this section!");
    }
}

با کد بالا مشکلی در اجرای کد وجود نخواهد داشت، به این خاطر که در زمان ساخت principal دسترسی UsersAdmin برای کاربر تعریف شده و کاربر می تواند به متد CheckPrincipal دسترسی داشته باشد. علاوه بر متد IsInRole می توان از Attribute ای به نام PrincipalPermission نیز برای کنترل دسترسی استفاده کرد. متد CheckPrincipal را در کد بالا بوسیله PrincipalPermission مجدد پیاده سازی می کنیم :

[PrincipalPermission(SecurityAction.Demand, Role = "Other")]
public static void CheckPrincipal()
{
    Console.WriteLine("Welcome");
}

با فراخوانی متد بالا پیغام خطایی به صورت زیر دریافت می کنیم :

Unhandled Exception: System.Security.SecurityException: Request for principal permission failed.

به این خاطر که دسترسی Other برای principal تعریف نشده است، اما اگر به جای Other از UsersAdmin استفاده می کردیم، متد بالا بدون مشکل فراخوانی می شود. علاوه بر اینکه می توان از PrincipalPermission به صورت Attribute استفاده کرد، امکان استفاده از آن به صورت مستقیم در کد نیز وجود دارد:

new PrincipalPermission(null, "Other").Demand();

نویسنده: حسین احمدی

منبع: ITpro


حسین احمدی
حسین احمدی

بنیانگذار توسینسو و برنامه نویس و توسعه دهنده ارشد وب

حسین احمدی ، بنیانگذار TOSINSO ، توسعه دهنده وب و برنامه نویس ، بیش از 12 سال سابقه فعالیت حرفه ای در سطح کلان ، مشاور ، مدیر پروژه و مدرس نهادهای مالی و اعتباری ، تخصص در پلتفرم دات نت و زبان سی شارپ ، طراحی و توسعه وب ، امنیت نرم افزار ، تحلیل سیستم های اطلاعاتی و داده کاوی ...

نظرات