سؤال

أقوم بترحيل تطبيق SaaS من Classic ASP إلى .NET MVC5 وسأستخدم قاعدة بيانات EF6 أولاً.يمكن تخصيص نموذج تسجيل الدخول للمستخدمين النهائيين من قبل كل مستأجر (على المجال الفرعي الخاص بهم ولكن مع الإشارة إلى نفس تطبيق الويب).نرغب في استخدام مخطط قاعدة البيانات الحالي ومرشحات المصادقة والترخيص الجديدة.

على سبيل المثال، يمكن لمستخدم أحد المستأجرين تسجيل الدخول عن طريق إدخال اسمه الأول ولقبه والرمز الذي أنشأه نظامنا.يمكن لمستخدم على مستأجر آخر تسجيل الدخول عن طريق إدخال عنوان البريد الإلكتروني وكلمة المرور الخاصة به.بالإضافة إلى ذلك، كل مستأجر لديه تسجيل دخول مسؤول منفصل يستخدم اسم المستخدم وكلمة المرور.يجوز لمستأجر آخر استخدام مصادقة LDAP مقابل خادم AD بعيد.

هل هناك طريقة محددة لأفضل الممارسات لإجراء المصادقة المخصصة؟

يبدو أن كل مقال تقريبًا يقترح طرقًا مختلفة لتحقيق ذلك:ببساطة الإعداد FormsAuthentication.SetAuthCookie, ، باستخدام موفر OWIN مخصص، قم بالتجاوز AuthorizeAttribute, ، إلخ.

في Classic ASP، قمنا بالاستعلام عن قاعدة البيانات لمعرفة نوع تسجيل الدخول لهذا المستأجر، وعرضنا الحقول المناسبة على شاشة تسجيل الدخول ثم عند إعادة النشر، وفحصنا تطابق الحقول مع ما هو موجود في قاعدة البيانات، ثم قمنا بتعيين متغيرات الجلسة بشكل مناسب والتي كانت فحص على كل طلب الصفحة.

شكرًا

هل كانت مفيدة؟

المحلول

أجد أن إطار عمل الهوية مرن جدًا من حيث خيارات المصادقة.ألقِ نظرة على هذا الجزء من رمز المصادقة:

var identity = await this.CreateIdentityAsync(applicationUser, DefaultAuthenticationTypes.ApplicationCookie);

authenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = isPersistent }, identity);

يعد هذا تشغيلًا قياسيًا جدًا لجزء المصادقة في الهوية، وستجد هذا في كل عينة هوية على الويب.إذا نظرت عن كثب فهو مرن للغاية - كل ما تحتاجه للمصادقة هو ApplicationUser كائن هذا الإطار لا يهتم بكيفية الحصول عليه.

لذلك من الناحية النظرية يمكنك القيام بأشياء مثل هذا (الرمز الكاذب، لم أحاول تجميع هذا):

// get user object from the database with whatever conditions you like
// this can be AuthCode which was pre-set on the user object in the db-table
// or some other property
var user = dbContext.Users.Where(u => u.Username == "BillyJoe" && u.Tenant == "ExpensiveClient" && u.AuthCode == "654")

// check user for null 

// check if the password is correct - don't have to do that if you are doing
// super-custom auth.
var isCorrectPassword = await userManager.CheckPasswordAsync(user, "enteredPassword");

if (isCorrectPassword)
{
    // password is correct, time to login
    // this creates ClaimsIdentity object from the ApplicationUser object
    var identity = await this.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie);

    // now we can set claims on the identity. Claims are stored in cookie and available without
    // querying database
    identity.AddClaim(new Claim("MyApp:TenantName", "ExpensiveClient"));
    identity.AddClaim(new Claim("MyApp:LoginType", "AuthCode"));
    identity.AddClaim(new Claim("MyApp:CanViewProducts", "true"));


    // this tells OWIN that it can set auth cookie when it is time to send 
    // a reply back to the client
    authenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = isPersistent }, identity);
}

باستخدام هذه المصادقة، قمت بتعيين بعض المطالبات على المستخدم - يتم تخزينها في ملف تعريف الارتباط ومتاحة في كل مكان عبر ClaimsPrincipal.Current.Claims.المطالبات هي في الأساس مجموعة من أزواج السلاسل ذات القيمة الرئيسية ويمكنك تخزين أي شيء تريده هناك.

عادةً ما أتمكن من الوصول إلى المطالبات المقدمة من المستخدم عبر طريقة الامتداد:

public static String GetTenantName(this ClaimsPrincipal principal)
{
    var tenantClaim = principal.Claims.FirstOrDefault(c => c.Type == "MyApp:TenantName");
    if (tenantClaim != null)
    {
        return tenantClaim.Value;
    }

    throw new ApplicationException("Tenant name is not set. Can not proceed");
}

public static String CanViewProducts(this ClaimsPrincipal principal)
{
    var productClaim = principal.Claims.FirstOrDefault(c => c.Type == "MyApp:CanViewProducts");
    if (productClaim == null)
    {
        return false;
    }

    return productClaim.Value == "true";
}

لذا، في طبقة التحكم/العرض/الأعمال الخاصة بك، يمكنك دائمًا الاتصال بها ClaimsPrincipal.Current.GetTenantName() وفي هذه الحالة ستحصل على "ExpensiveClient" مرة أخرى.

أو إذا كنت بحاجة إلى التحقق من تمكين ميزة معينة للمستخدم، فعليك القيام بذلك

if(ClaimsPrincipal.Current.CanViewProducts())
{
    // display products
}

الأمر متروك لك لكيفية تخزين خصائص المستخدم الخاصة بك، ولكن طالما قمت بتعيينها كمطالبات على ملف تعريف الارتباط، فستكون متاحة.

وبدلاً من ذلك، يمكنك إضافة مطالبات إلى قاعدة البيانات لكل مستخدم:

await userManager.AddClaimAsync(user.Id, new Claim("MyApp:TenantName", "ExpensiveClient"));

وهذا سوف يستمر المطالبة في قاعدة البيانات.وبشكل افتراضي، يضيف إطار عمل الهوية هذه المطالبة إلى المستخدم عند تسجيل الدخول دون الحاجة إلى إضافتها يدويًا.

ولكن احذر، لا يمكنك تعيين عدد كبير جدًا من المطالبات على ملف تعريف الارتباط.تحتوي ملفات تعريف الارتباط على حد 4K تحدده المتصفحات.والطريقة التي يعمل بها تشفير ملفات تعريف الارتباط للهوية تعمل على زيادة النص المشفر بنحو 1.1، بحيث يمكن أن يكون لديك ما يقرب من 3.6 كيلو بايت من النص الذي يمثل المطالبات.لقد واجهت هذا قضية هنا

تحديث

للتحكم في الوصول إلى وحدات التحكم عبر المطالبات، يمكنك استخدام الفلتر التالي على وحدة التحكم:

public class ClaimsAuthorizeAttribute : AuthorizeAttribute
{
    public string Name { get; private set; }


    public ClaimsAuthorizeAttribute(string name)
    {
        Name = name;
    }

    public override void OnAuthorization(AuthorizationContext filterContext)
    {
        var user = HttpContext.Current.User as ClaimsPrincipal;
        if (user.HasClaim(Name, Name))
        {
            base.OnAuthorization(filterContext);
        }
        else
        {
            filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary()
            {
                {"controller", "errors"},
                {"action", "Unauthorised"}
            });
        }
    }
}

ثم استخدم هذه السمة على وحدات التحكم أو الإجراءات المنفصلة مثل هذا:

    [ClaimsAuthorize("Creating Something")]
    public ActionResult CreateSomething()
    {
        return View();
    }

سيطلب المستخدم مطالبة "إنشاء شيء ما" للوصول إلى هذا الإجراء، وإلا ستتم إعادة توجيهه إلى صفحة "غير مصادق عليه".

لقد تعاملت مؤخرًا مع مصادقة المطالبات وقمت بإنشاء تطبيق نموذجي مشابه لمتطلباتك.يرجى إلقاء نظرة على النسخة البسيطة: https://github.com/trailmax/ClaimsAuthorisation/tree/SimpleClaims حيث يتم تخزين المطالبات بشكل فردي لكل مستخدم.أو هناك حل أكثر تعقيدًا حيث تنتمي المطالبات إلى دور ما، وعندما يقوم المستخدمون بتسجيل الدخول، يتم تعيين مطالبات الدور للمستخدم: https://github.com/trailmax/ClaimsAuthorisation/tree/master

نصائح أخرى

هناك مكونان تحتاجهما.المصادقة نفسها والاستراتيجية التي يحصل عليها كل مستخدم للمصادقة.

الأول سهل ويتم إنجازه بهذين السطرين...

var identity = await UserManager.CreateIdentityAsync(user, 
    DefaultAuthenticationTypes.ApplicationCookie);
AuthenticationManager.SignIn(new AuthenticationProperties() 
    { IsPersistent = isPersistent }, identity);

عندما يقوم المستخدم بتسجيل الدخول، فإنه يحصل على هوية تحتوي على مطالبات المستخدم بشأن الأدوار ومن هو.يتم منحها للمستخدم كملف تعريف الارتباط.بعد هذه النقطة عليك فقط تزيين وحدات التحكم بها [Authorize] للتأكد من أن المستخدمين المصادق عليهم فقط يمكنهم تسجيل الدخول.مستوى جميل هنا.

الجزء المعقد الوحيد في المشكلة هو الجزء الثاني؛يتم تعيين الإستراتيجية الخاصة بكيفية مصادقة كل مستخدم بواسطة المسؤول.

بعض الكود الكاذب لكيفية عمل ذلك في الإجراءات هو هذا ...

// GET: /Account/Login
[AllowAnonymous]
public ActionResult Login(int tenantId)
{
    var tenant = DB.GetTenant(tenantId);
    return View(tenant);
}

من وجهة نظرك، ستخرج استراتيجية المصادقة للمستأجر.قد يكون ذلك بريدًا إلكترونيًا وكلمة مرور، أو رمزًا وبريدًا إلكترونيًا، أو أيًا كانت متطلباتك.

عندما يقوم المستخدم بإدخال معلوماته والنقر لتسجيل الدخول، يتعين عليك بعد ذلك تحديد الإستراتيجية التي كان يستخدمها، والتحقق لمعرفة ما إذا كانت معلوماته متطابقة.

//
// POST: /Account/Login
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(LoginViewModel model)
{
    var tenant = DB.GetTenant(model.tenantId);
    //If user info matches what is expected for the tenants strategy
    if(AuthenticateUserInfo(tenant, model.UserInputs))
    {
       //Sign the user in
       var identity = await UserManager.CreateIdentityAsync(user, 
           DefaultAuthenticationTypes.ApplicationCookie);
       AuthenticationManager.SignIn(new AuthenticationProperties() 
           { IsPersistent = isPersistent }, identity);
    }
}

لقد قمت بالكثير من التلويح باليد في الجزء الثاني بسبب الطبيعة المعقدة لمدى ديناميكيتها.بشكل عام، يجب عليك استخدام نفس الاستراتيجيات التي استخدمتها في تطبيقك القديم لإنشاء المدخلات الصحيحة وما شابه.لم يتغير شيء هناك، فقط طريقة تسجيل الدخول ستكون مختلفة.

باستخدام Visual Studio 2013 التحديث 3 يمكنك إنشاء تطبيق ويب جديد الذي يأتي مع MVC5, EF6 والهوية مثبتا بالفعل.هنا هو كيفية تحديد الهوية عند إنشاء تطبيق جديد:

مع MVC القالب المحدد ، انقر فوق تغيير المصادقة و أبرز نافذة منبثقة.حسابات المستخدم الفردية = الهوية.انقر فوق موافق للمتابعة.Select Identity in MVC Application

بعد أن فعلت ذلك ، قمت بإنشاء تطبيق مع الهوية.يمكنك الآن تخصيص الخاص بك تسجيل الدخول التسجيل على النحو التالي.

كنت تريد أن تبدو في AccountController.cs في وحدات تحكم مجلد.هنا سوف تجد البرنامج النصي من أجل التسجيل و تسجيل الدخول.

إذا نظرتم إلى

public async Task<ActionResult> Register(RegisterViewModel model)

وظيفة, ستلاحظ أنه يحتوي على:

IdentityResult result = await UserManager.CreateAsync(new ApplicationUser() { UserName = newUser.UserName }, newUser.Password);

هذا هو المكان الذي يحصل المستخدم التي تم إنشاؤها.إذا كنت ترغب في استخدام الهوية ، يجب حفظ المستخدمين اسم المستخدم وكلمة المرور.يمكنك استخدام البريد الإلكتروني واسم المستخدم إذا كنت تريد.الخ.

بعد القيام بذلك, إضافة المستخدم المحدد دور (أجد المستخدم ومن ثم إضافته إلى دور):

ApplicationUser userIDN = UserManager.FindByName(newUser.UserName);
result = await UserManager.AddToRoleAsync(userIDN.Id, "Admin");

في السيناريو ، لقد خلق إضافية تمديد الجدول حيث أمسك بها العنوان ، رقم الهاتف ، إلخ.في هذا الجدول ، يمكنك إجراء أية معلومات تسجيل الدخول.يمكنك إضافة هذه الإدخالات الجديدة قبل أو بعد إنشاء حساب المستخدمين في الهوية.وأود أن إنشاء معلومات موسعة ثم إنشاء حساب الهوية فقط للتأكد.

المهم: أي سيناريوهات حيث المستخدم تسجيل الدخول مع شيء غير اسم المستخدم أو عنوان البريد الإلكتروني هذا ليس حفظها في طريق الهوية, سيكون لديك للقيام حل مخصص.

على سبيل المثال: أنواع المستخدم في الاسم الأول, اسم العائلة المدونة.يمكنك أن تفعل شيئين:حفظ الاسم الأول و اسم العائلة في حقل اسم المستخدم الهوية رمز إلى كلمة المرور و التحقق من تسجيل الدخول بهذه الطريقة أو يمكنك التحقق من جدول مخصص لأولئك خصائص والتأكد من أنها تطابق عندما هل يمكن أن نسمي هذا الجمال قليلا:

await SignInAsync(new ApplicationUser() { UserName = model.UserName }, isPersistent: false);

بمجرد الدعوة التي SignInAsync وظيفة يمكنك الذهاب مباشرة إلى الصفحة المحمية.

ملاحظة: أنا خلق ApplicationUser على وظيفة الدعوة ولكن إذا كنت تستخدم أكثر من مرة أنه سيكون من المثالي بالنسبة لك أن تعلن ApplicationUser على النحو التالي:

ApplicationUser user = new ApplicationUser() { UserName = model.UserName };

ملاحظة #2: إذا كنت لا تريد أن المستخدم المتزامن أساليب هذه الوظائف جميعها غير المتزامن الإصدارات منهم.

ملاحظة رقم 3: في أعلى أي صفحة باستخدام UserManagement ، فإنه يتم الإعلان عنها.تأكد إذا كنت تقوم بإنشاء الخاصة بك وحدة تحكم لم تكن تلك التي تم إنشاؤها بواسطة Visual Studio استخدام الهوية ، وتشمل UserManagement الإعلان النصي في أعلى داخل الصف:

namespace NameOfProject.Controllers
{
    [Authorize]
    public class AccountController : Controller
    {
        public AccountController() : this(new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(new ApplicationDbContext()))) { }
        public AccountController(UserManager<ApplicationUser> userManager) { UserManager = userManager; }
        public UserManager<ApplicationUser> UserManager { get; private set; }

واسمحوا لي أن أعرف إذا كان لديك أي أسئلة آمل أن يساعد هذا.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top