بکارگیری LDAP و Active Directory با سی شارپ

LDAP ، در این مقاله، Lightweight Directory Access Protoco ( پروتکل دسترسی سبک به دایرکتوری ) را مورد مطالعه قرار خواهیم داد.

پروتکلی که در دهه ی ۹۰ توسعه داده شد تا جایگزینی قابل دسترس و ساده تر برای دیگر پروتکل های مربوط به دایرکتوری باشد.

همچنین در رابطه با Active Directory (پیاده سازی Lightweight Directory Access Protoco مایکروسافت با ویژگی های بیشتر) و نحوه ی استفاده از آن به عنوان مکانیزمی برای احراز هویت صحبت خواهیم کرد.

در این مقاله بر روی LdapConnection API عمومی تمرکز خواهیم کرد.

LDAP چیست؟

LDAP پروتکلی است که مجموعه ای از اعمال را ، که از طریق آن می توانید به اطلاعاتی که بخشی از یک دایرکتوری هستند دسترسی پیدا کنید، تعریف می کند.

یک دایرکتوری، درختی حاوی مجموعه ای از صفات مربوط به یک شناسه ی منحصربفرد (یا کلید اصلی) است.

اگر با پایگاه داده های مبتنی بر سند آشنا باشید، ممکن است این موضوع برایتان آشنا به نظر برسد.

کلید اصلی معمولا یک نام است. این بدین معناست که Lightweight Directory Access Protoco کاملا مناسب یک پایگاه داده ی اطلاعات کاربر است.

اگرچه اغلب اوقات Lightweight Directory Access Protoco به عنوان یک دایرکتوری کاربر مورد استفاده قرار می گیرد، اما می تواند به عنوان یک سرویس اشتراک اطلاعات عمومی نیز کار کند.

LDAP

یک کاربرد رایج Lightweight Directory Access Protoco ، به عنوان بخشی از سیستم های (single-sign-on (SSO (شناسایی یگانه) است.

نمودار زیر نشان می دهد که یک سیستم ساده ی SSO چگونه می تواند با استفاده از Lightweight Directory Access Protoco کار کند.

نمودار، یک پیکربندی ساده شده ی Microsof Active Directory با استفاده از Lightweight Directory Access Protoco را نشان می دهد.

Active Directory اطلاعات کاربر را در یک سرور Lightweight Directory Access Protoco ذخیره می کند.

هنگامیکه یک کاربر اقدام به ورود به کامپیوتر شخصی ویندوزی خود می کند، ویندوز اطلاعات ورود به سیستم را در مقایسه با سرور LDAP/Active Directory اعتبارسنجی می کند.

هر زمان که کاربر سعی کند کاری انجام دهد که نیاز به احراز هویت داشته باشد، برنامه می تواند از اطلاعات سرور Active Directory برای اعتبارسنجی هویت کاربر استفاده کند.

البته، اگر SSO نیاز نباشد، Active Directory می تواند به عنوان یک مکانیزم احراز هویت ساده نیز استفاده شود.

LDAP

مروری بر پروتکل

بهترین روش برای درک یک پروتکل، دست بکار شدن و یادگیری کارکردهای داخلی آن است.

خوشبختانه، جدای از جزئیات کدگذاری باینری و دیگر موارد سطح پایین، Lightweight Directory Access Protoco پروتکل بسیار ساده ای است.

Lightweight Directory Access Protoco مجموعه ای از اعمال را که در دسترس سرویس گیرنده ها قرار دارد، تعریف می کند. سرویس گیرنده ها می توانند به دو نوع سرور متصل شوند:

  • Diretory System Agent (کارگزار سیستم دایرکتوری) : سروری که عملیات LDAP را فراهم می کند.
  • Global Catalog (کاتالوگ همگانی) : نوعی خاص از سرور که مجموعه ای کاهش داده شده از اطلاعات تکراری از DSA ها را برای سرعت بخشیدن به جستجوها ذخیره می کند.

“بهترین روش برای درک یک پروتکل، دست بکار شدن و یادگیری کارکردهای داخلی آن است.”

سرویس گیرندگان درخواست هایی را به سرور ارسال می کنند.

متعاقبا، سرور به آن درخواست ها پاسخ می دهد. اکثر درخواست ها ناهمزمان هستند.

دیگر درخواست ها الزاما همزمان هستند (مانند تبادل/دست دادن اتصال [connection handshake]).

بعلاوه، سرور ممکن است پیام هایی خاص به سرویس گیرندگان بفرستد حتی زمانیکه هیچ درخواست معوقی که نیاز به پاسخ داشته باشد موجود نباشد (برای مثال، سرور ممکن است پیامی بفرستد تا به سرویس گیرندگان اطلاع دهد که دارد خاموش می شود).

تمامی اطلاعات با استفاده از ASN.1 کدگذاری می شود .

TSL و/یا SASL ممکن است برای اطمینان از حریم خصوصی و انجام احراز هویت استفاده شوند.

LDAP

عملیات های پشتیبانی شده

اعمال زیر دارای نمونه های .NET هستند. می توانید یک مثال کاملا عملی را در بخش مثال زیر بیابید.
عملیات های رایج پشتیبانی شده توسطLightweight Directory Access Protoco این ها هستند:

  • جستجوی ورودی های خاص.
var request = new SearchRequest("ou=users,dc=example,dc=com", "(objectClass=simpleSecurityObject)", SearchScope.Subtree, null);
var response = (SearchResponse)connection.SendRequest(request);
foreach(SearchResultEntry entry in response.Entries)
{
    //Process the entries
}
  • آزمون جهت تعیین اینکه آیا یک صفت خاص مشخص حضور داشته و دارای مقدار مشخص شده است یا خیر.
var request = new CompareRequest("uid=test,ou=users,dc=example,dc=com", "userPassword", "{SSHA}dFyxYbqyPKlQ7Py1T14XupyVfz7UFIz+");
var response = (CompareResponse)connection.SendRequest(request);
if(response.ResultCode == ResultCode.CompareTrue)
{
    //Attribute present and has the right value
}
  • افزودن/تغییر/حذف ورودی ها.
var request = new AddRequest("uid=test,ou=users,dc=example,dc=com", new DirectoryAttribute[] {
    new DirectoryAttribute("uid", "test"),
    new DirectoryAttribute("ou", "users"),
    new DirectoryAttribute("userPassword", "badplaintextpw"),
    new DirectoryAttribute("objectClass", new string[] { "top", "account", "simpleSecurityObject" })
});
connection.SendRequest(request);
  • انتقال یک ورودی به مسیری متفاوت.
var request = new ModifyDNRequest("uid=test,ou=users,dc=example,dc=com", "ou=administrators,dc=example,dc=com", "uid=test");
connection.SendRequest(request);

علاوه بر این، عملیات های مدیریت پروتکل بیشتری تعریف شده اند (اتصال، قطع اتصال، تبادل نسخه ی پروتکل، …).

کدگذاری ASN.1 و BER

تمامی اعمال با بکارگیری پیام های کدگذاری شده در فرمت Abstract Syntax Notation One ) ASN.1) نمادگذاری انتزاعی  با استفاده از Basic Encoding Rules )BER)قوانین کدگذاری پایه انجام می شوند.

ASN.1 در ITU standard X.680 تعریف شده است، در حالیکه BER و دیگر کدگذاری ها بخشی از ITU standard X.690 هستند.

ASN.1 دنباله ای از انواع داده ای (از جمله عدد صحیح، رشته و …)، یک توصیف قالب متنی (شما یاطرح)، و یک نمایش متنی از مقادیر را تعریف می کند.

BER، از سوی دیگر، یک کدگذاری دودویی برای ASN.1 تعریف می کند.

BER یک کدگذاری مرسوم برای مقدار طول برچسب (tag-length-value) است.

در اینجا ،طرحی مستقیما از RFC مربوط به LDAP آورده شده است که قالب پیام برای LDAP را نشان می دهد:

LDAPMessage ::= SEQUENCE {
     messageID       MessageID,
     protocolOp      CHOICE {
          bindRequest           BindRequest,
          bindResponse          BindResponse,
          unbindRequest         UnbindRequest,
          searchRequest         SearchRequest,
          searchResEntry        SearchResultEntry,
          searchResDone         SearchResultDone,
          searchResRef          SearchResultReference,
          modifyRequest         ModifyRequest,
          modifyResponse        ModifyResponse,
          addRequest            AddRequest,
          addResponse           AddResponse,
          delRequest            DelRequest,
          delResponse           DelResponse,
          modDNRequest          ModifyDNRequest,
          modDNResponse         ModifyDNResponse,
          compareRequest        CompareRequest,
          compareResponse       CompareResponse,
          abandonRequest        AbandonRequest,
          extendedReq           ExtendedRequest,
          extendedResp          ExtendedResponse,
          ...,
          intermediateResponse  IntermediateResponse },
     controls       [0] Controls OPTIONAL }

MessageID ::= INTEGER (0 ..  maxInt)

در مثال فوق، می توانیم مشاهده کنیم که یک پیام LDAP حامل شناسه ی پیام (که عددی صحیح از ۰ تا maxInt است)، شیء مربوط به عملیات (که هر شیء در جایی دیگر تعریف شده است)، و یک فیلدیا بخش اضافی به نام control (که در جایی دیگر در طرح تحت عنوان control تعریف شده) است.

LDAP از طریق علامت گذاری مشابه با قالب داده ای که از آن بصورت داخلی (درون کد مربوط به خود LDAP) استفاده می کند، تعریف می شود. قدرت ASN.1 را نظاره کنید!

مثالی ساده تر با داده های واقعی:

World-Schema DEFINITIONS AUTOMATIC TAGS ::=
BEGIN
  Human ::= SEQUENCE {
     name UTF8String,
     first-words UTF8String DEFAULT "Hello World",
     age  CHOICE {
        biblical INTEGER (1..1000),
        modern  INTEGER (1..100)
     } OPTIONAL
  }
END

first-man Human ::=
{
    name "Adam",
    -- use default for first-words --
    age biblical: 930
}

در این مثال ابتدا طرحی برای یک human (انسان) را مشاهده می کنیم.

یک human دارای دو فیلد/بخش مورد نیاز name و first-words و یک فیلد اختیاری age است.

فیلد first-words، اگر در مدل موجود نباشد، مقدار پیش فرض “Hello World” را می گیرد.

فیلد age نیز می تواند یکی از این دو گزینه باشد: biblical (هر عدد صحیحی از ۱ تا ۱۰۰۰) یا modern (هر عدد صحیحی از ۱ تا ۱۰۰).

یک مدل human مطابق با طرح فوق به دنبال این طرح می آید (انسانی به نام “Adam”، دارای مقدار پیش فرض برای first-words، با سن  ۹۳۰).

LDAP Data Interchange Format ) LDIF) ]قالب تبادل داده ی LDAP

اگرچه LDAP از ASN.1 بصورت داخلی (درون کد خود) استفاده می کند، و ASN.1 می تواند به عنوان متن نمایش داده شود، با این حال، نمایش متنی متفاوتی برای اطلاعات LDAP به نام LDAP Data Interchange Format ) LDIF) وجود دارد.

در اینجا نمونه ای آورده شده است:

dn: cn=Barbara J Jensen,dc=example,dc=com
cn: Barbara J Jensen
cn: Babs Jensen
objectclass: person
description: file:///tmp/babs
sn: Jensen

صفات دو حرفی در مثال فوق این ها هستند:

  •  dn: distinguished name (نام متمایز)
  •  dc: domain component (مؤلفه ی دامنه)
  •  cn: common name (نام متداول)
  • sn: surname (لقب)

LDIF می تواند به عنوان ابزاری برای اجرای اعمال نیز استفاده شود:

dn: cn=Babs Jensen,dc=example,dc=com
changetype: modify
add: givenName
givenName: Barbara
givenName: babs

مثال های بالا بیانگر این هستند که نام متمایز (DN)، ورودی را بطور منحصر بفردی شناسایی می کند.

هنگامیکه صحبت از LDAP باشد، LDIF بسیار رایج تر از دیگر گونه ها است.

در واقع، ابزارهایی مانند OpenLDAP از LDIF به عنوان ورودی/خروجی استفاده می کنند.

مثال: بکارگیری LDAP از یک سرویس گیرنده ی سی شارپ

.NET مجموعه ای مناسب و قابل دسترس از کلاس ها را برای دسترسی به سرورهای LDAP و Active Directory فراهم می کند.

در اینجا اسناد .NET مربوطه آورده شده است.

مثال زیر در مقایسه با Open LDAP 2.4 آزموده شده است.

مدل کاربری برای مثال ما شامل فیلدهای زیر است:

  •  uid: user id ) name) : شناسه ی کاربری
  • ou: organizational unit : واحد سازمانی
  • userPassword: hashed user password : رمز عبور هش شده ی کاربر (هَش نوعی ذخیره سازی داده ها و نیز رمزگذاری آن هاست)
  • objectClass : typical classes for user accounts کلاس های معمول/رایج برای حساب ها

توجه داشته باشید که این مدل برای کاربر Active Directory نیست. کاربران Active Directory می¬توانند با استفاده از عملیات bind (پیوند) اعتبارسنجی شوند (ادامه را ببینید).

اعتبارسنجی اعتبارنامه های کاربر با استفاده از bind

در عمل، اعتبارنامه های ذخیره شده در یک دایرکتوری LDAP با استفاده از عملیات bind اعتبارسنجی می شوند.

عملیات bind به معنای “ورود به یک سرور LDAP با استفاده از مجموعه ای مشخص از اعتبارنامه ها” است.

اگر عملیات bind موفقیت آمیز باشد، بدین معناست که اعتبارنامه ها معتبر هستند.

نگاشت کاربر به یک ورودی واقعی در دایرکتوری LDAP، در پیکربندی سرور تنظیم و انجام می شود (Active Directory قوانین مشخصی برای این امر دارد؛ دیگر سرورهای LDAP این جزئیات را به مدیر واگذار می کنند).

/// 
<summary>
/// Another way of validating a user is to perform a bind. In this case, the server
/// queries its own database to validate the credentials. The server defines
/// how a user is mapped to its directory.
/// </summary>

/// <param name="username">Username</param>
/// <param name="password">Password</param>
/// <returns>true if the credentials are valid, false otherwise</returns>
public bool validateUserByBind(string username, string password)
{
    bool result = true;
    var credentials = new NetworkCredential(username, password);
    var serverId = new LdapDirectoryIdentifier(connection.SessionOptions.HostName);

    var conn = new LdapConnection(serverId, credentials);
    try
    {
        conn.Bind();
    }
    catch (Exception)
    {
        result = false;
    }

    conn.Dispose();

    return result;
}

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

اگر به اعتبارنامه های ذخیره شده در دایرکتوری دسترسی کامل دارید، می توانید رمز عبورهای هش شده ی کاربران خود را جهت اعتبارسنجی اعتبارنامه ها بررسی و مقایسه کنید.

توجه داشته باشید که نحوه ی ذخیره سازی اعتبارنامه ها توسط Active Directory بدین صورت نیست.

کاربران در یک سرور Active Directory باید با استفاده از عملیات “bind” اعتبارسنجی شوند.

مثال قبل را برای اطلاع از نحوه ی اجرای یک عملیات bind با استفاده از API ببینید.

/// 
<summary>
/// Searches for a user and compares the password.
/// We assume all users are at base DN ou=users,dc=example,dc=com and that passwords are
/// hashed using SHA1 (no salt) in OpenLDAP format.
/// </summary>

/// <param name="username">Username</param>
/// <param name="password">Password</param>
/// <returns>true if the credentials are valid, false otherwise</returns>
public bool validateUser(string username, string password)
{
    var sha1 = new SHA1Managed();
    var digest = Convert.ToBase64String(sha1.ComputeHash(System.Text.Encoding.UTF8.GetBytes(password)));
    var request = new CompareRequest(string.Format("uid={0},ou=users,dc=example,dc=com", username),
        "userPassword", "{SHA}" + digest);
    var response = (CompareResponse)connection.SendRequest(request);
    return response.ResultCode == ResultCode.CompareTrue;
}

برقراری اتصال

public Client(string username, string domain, string password, string url)
{
    var credentials = new NetworkCredential(username, password, domain);
    var serverId = new LdapDirectoryIdentifier(url);

    connection = new LdapConnection(serverId, credentials);
}

پارامترهای اتصال مورد استفاده در این مثال موارد زیر هستند:

  •  username (نام کاربری): test
  • domain (دامنه): example.com
  •  password (رمز عبور): test
  • url (نشانی): localhost:389

پیکربندی سرور خود را برای انتخاب پارامترهای اتصال مناسب بررسی کنید.

اگر از LDAP + SASL استفاده می کنید، فراموش نکنید که پارامترهای SASL مناسب را در فایل پیکربندی OpenLDAP تنظیم کنید.

برای نمونه، خط زیر به OpenLDAP می گوید که بطور مستقیم از پایگاه داده ی SASL استفاده کند.

sasl-auxprops sasldb

افزودن کاربر به دایرکتوری

/// 
<summary>
/// Adds a user to the LDAP server database. This method is intentionally less generic than the search one to
/// make it easier to add meaningful information to the database.
/// NOTE: this is not an Active Directory user.
/// </summary>

/// <param name="user">The user to add</param>
public void addUser(UserModel user)
{
    var sha1 = new SHA1Managed();
    var digest = Convert.ToBase64String(sha1.ComputeHash(System.Text.Encoding.UTF8.GetBytes(user.UserPassword)));

    var request = new AddRequest(user.DN, new DirectoryAttribute[] {
        new DirectoryAttribute("uid", user.UID),
        new DirectoryAttribute("ou", user.OU),
        new DirectoryAttribute("userPassword", "{SHA}" + digest),
        new DirectoryAttribute("objectClass", new string[] { "top", "account", "simpleSecurityObject" })
    });

    connection.SendRequest(request);
}

حاشیه: راه اندازی Auth 0 جهت بکارگیری LDAP

در Auth 0 به سرویس گیرندگان اهمیت دارند.

اگر آرایشی از LDAP در اختیار دارید، می توانید آن را با Auth 0 تلفیق کنید.

آرایش های LDAP معمولا درون یک شبکه ی سازمانی نصب می شوند.

به عبارت دیگر، خصوصی هستند و به همین جهت، هیچ نوع دسترسی خارجی به سرور LDAP وجود ندارد.

از آنجاییکه solution (راه حل) احراز هویت ما از طریق cloud (فضای ابری) کار می کند، لازم است ابزاری برای ارتباط شبکه ی داخلی با سرورهای خود فراهم کرد.

این چیزی است که ما در قالب رابط Active Directory / LDAP فراهم می آوریم.

این رابط، سرویسی است که در شبکه ی شما نصب می شود تا پلی میان سرور LDAP شما و سرورهای ما در cloud فراهم کند.

نگران نباشید! رابط از اتصالی برون سو (outbound connection) به سرورهای ما استفاده می کند تا نیاز نباشد قوانین خاصی در firewall/دیواره ی آتش خود تنظیم کنید.

LDAP

جهت فعالسازی LDAP برای برنامه های Auth0 خود، ابتدا به Connections -> Enterprise -> Active Directory / LDAP بروید.

مراحل را برای راه اندازی رابط LDAP دنبال کرده (به جزئیات سرور LDAP نیاز خواهید داشت) و سپس LDAP را برای برنامه ی خود فعال کنید.

مثال های زیر برای مثال سی شارپ که ذکر شد از راه اندازی سرور LDAP استفاده می کنند.

Auth0 + LDAP با استفاده از C

هنگامیکه LDAP را در داشبورد فعال و ارتباط را راه اندازی کردید، می توانید مراحل معمول را برای جریان Resource Owner Password (رمز عبور صاحب منبع) دنبال کنید.

ورود به سیستم تنها با استفاده از یک ایمیل و رمز عبور کار می کند!

public class RequestBody
{
    [JsonProperty("client_id")]
    public string ClientId { get; set; }
    [JsonProperty("audience")]
    public string Audience { get; set; }
    [JsonProperty("grant_type")]
    public string GrantType { get; set; }
    [JsonProperty("username")]
    public string Username { get; set; }
    [JsonProperty("password")]
    public string Password { get; set; }
    [JsonProperty("scope")]
    public string Scope { get; set; }
    [JsonProperty("realm")]
    public string Realm { get; set; }
}

public class ResponseBody
{
    [JsonProperty("access_token")]
    public string AccessToken;
    [JsonProperty("id_token")]
    public string IdToken;
    [JsonProperty("expires_in")]
    public string ExpiresIn;
    [JsonProperty("scope")]
    public string Scope;
    [JsonProperty("token_type")]
    public string TokenType;
}

class Program
{
    static HttpClient client = new HttpClient();

    static void Main(string[] args)
    {
        RunAsync().GetAwaiter().GetResult();
    }

    static async Task RunAsync()
    {
        client.BaseAddress = new Uri("YOUR-AUTH0-DOMAIN");
        client.DefaultRequestHeaders.Accept.Clear();
        client.DefaultRequestHeaders.Accept.Add(
            new MediaTypeWithQualityHeaderValue("application/json"));

        try
        {
            RequestBody req = new RequestBody
            {
                ClientId = "YOUR-AUTH0-CLIENT-ID",
                Audience = "YOUR-API-AUDIENCES", // See https://auth0.com/docs/api-auth
                GrantType = "http://auth0.com/oauth/grant-type/password-realm", // See https://auth0.com/docs/api-auth/tutorials/password-grant#realm-support
                Realm = "YOUR-LDAP-CONNECTION",
                Scope = "openid profile email YOUR-ADDITIONAL-SCOPES"
                Username = "test",
                Password = "test"
            };

            HttpResponseMessage response = await client.PostAsJsonAsync("oauth/token", req);
            response.EnsureSuccessStatusCode();
            ResponseBody resp = await response.Content.ReadAsAsync<ResponseBody>();
            // You can use the ID token to get user information in the frontend.
            string idToken = resp.IdToken;
            // You can use this token to interact with server-side APIs.
            string accessToken = resp.AccessToken;
        }
        catch (Exception e)
        {
            Console.WriteLine(e.Message);
        }
    }
}

Auth0 + LDAP با استفاده از REST API

می توانید با استفاده از RESTful API برای پایگاه داده، بدون رمز عبور و کاربران LDAP ما وارد سیستم شوید.

curl -H 'Content-Type: application/json' -X POST -d '{ "grant_type":"http://auth0.com/oauth/grant-type/password-realm", "realm": "your-ldap-connection", "client_id":"FyFnhDX2kSqtpMZ6pGe6QpQuJmD7s4dj", "username":"test", "password":"test" }' https://speyrott.auth0.com/oauth/token

نتیجه گیری

LDAP به عنوان پروتکلی سبک که می تواند به محتواهای دایرکتوری دسترسی پیدا کند، طراحی شد.

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

سادگی و قابل دسترس بودن آن، باعث شده LDAP با گذشت چندین سال به کار خود ادامه دهد.

امروزه، سیستم های شناسایی یگانه می توانند با استفاده از LDAP نیز کار کنند.

خوشبختانه، تلفیق LDAP با پروژه های موجود از قبل یا جدید آسان است.

زهره سلطانیان

نوشته‌های مرتبط

دیدگاه‌ها

*
*

این سایت از اکیسمت برای کاهش هرزنامه استفاده می کند. بیاموزید که چگونه اطلاعات دیدگاه های شما پردازش می‌شوند.