ربط المصادقة متعددة العوامل متعددة العوامل في .NET Core

تصف المقالة طريقة لربط المصادقة متعددة العوامل لموقع يعمل على النظام الأساسي .net باستخدام آليات التفويض المضمنة.


بضع كلمات لماذا هناك حاجة إلى المصادقة متعددة العوامل على الإطلاق:


  1. سلامة
  2. السلامة مرة أخرى
  3. السهولة أو الراحة

نعم ، النقطة الأخيرة ليست خطأ. عامل المصادقة الثاني و / أو الثالث ليس فقط إضافة إلى كلمة المرور التقليدية ، ولكن أيضًا بديل كامل. بدلاً من الرسائل القصيرة التي تحتوي على الرمز الذي يجب مقاطعته على الموقع ، فإن الأساليب الحديثة هي PUSH في برنامج المراسلة مع زر لتأكيد الإجراء أو المصادقة البيومترية باستخدام بصمة على الهاتف أو الكمبيوتر المحمول.


مبدأ التشغيل


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

رمز وصول


— JWT (JSON Web Token). . , , JSON . : , . base64-url. ,


{
  "typ": "JWT",     // : JWT
  "alg": "HS256"    // : HMAC   SHA-256
}

, , , , . : , claims . , iss, aud, sub .


{
  "iss": "https://access.multifactor.ru",   // 
  "aud": "https://example.com",             // 
  "sub": "user@example.com",                // 
  "jti": "RxMEyo9",                         //id 
  "iat": 1571684399,                        // 
  "exp": 1571684699,                        // 
  "returnUrl": "/",                         // 
  "rememberMe": "False",                    // 
  "createdAt": "10/21/19 6:59:55 PM"        // 
}

JWT — , , HMAC-SHA256(message, secret), :


  • message — , base64-url ;
  • secret — , , .

JWT , , , , e-mail, .
JWT , , . , .


, ASP.NET Core Web Application. :


  1. .


appsettings.json API multifactor.ru


"Multifactor": {
    "ApiKey": "",
    "ApiSecret": "",
    "CallbackUrl": "https://localhost:44300/account/mfa"
  }

  • ApiKey ApiSecret
  • CallbackUrl —

API


API multifactor.ru


/// <summary>
/// Multifactor Client
/// </summary>
public class MultifactorService
{
    //    API
    private string _apiKey;
    private string _apiSecret;
    private string _callbackUrl;

    private string _apiHost = "https://api.multifactor.ru";

    public MultifactorService(string apiKey, string apiSecret, string callbackUrl)
    {
        _apiKey = apiKey ?? throw new ArgumentNullException(nameof(apiKey));
        _apiSecret = apiSecret ?? throw new ArgumentNullException(nameof(apiSecret));
        _callbackUrl = callbackUrl ?? throw new ArgumentNullException(nameof(callbackUrl));
    }

    public async Task<string> GetAccessPage(string identityName, IDictionary<string, string> claims = null)
    {
        if (string.IsNullOrEmpty(identityName)) throw new ArgumentNullException(nameof(identityName));

        var request = JsonConvert.SerializeObject(new
        {
            Identity = identityName,    //login 
            Callback = new
            {
                Action = _callbackUrl,  // 
                Target = "_self"
            },
            Claims = claims             // 
        });

        var payLoad = Encoding.UTF8.GetBytes(request);

        //basic authorization
        var authHeader = Convert.ToBase64String(Encoding.ASCII.GetBytes(_apiKey + ":" + _apiSecret));

        using var client = new WebClient();
        client.Headers.Add("Authorization", "Basic " + authHeader);
        client.Headers.Add("Content-Type", "application/json");

        var responseData = await client.UploadDataTaskAsync(_apiHost + "/access/requests", "POST", payLoad);
        var responseJson = Encoding.ASCII.GetString(responseData);

        var response = JsonConvert.DeserializeObject<MultifactorResponse<MultifactorAccessPage>>(responseJson);
        return response.Model.Url; //  
    }

    internal class MultifactorResponse<T>
    {
        public bool Success { get; set; }
        public T Model { get; set; }
    }

    internal class MultifactorAccessPage
    {
        public string Url { get; set; }
    }
}

— API . , appsettings.json. GetAccessPage , API .



Startup.cs, ConfigureServices API


//load Multifactor settings
var multifactorSection = Configuration.GetSection("Multifactor");
var apiKey = multifactorSection["ApiKey"];
var apiSecret = multifactorSection["ApiSecret"];
var callbackUrl = multifactorSection["CallbackUrl"];

//register Multifactor service
var multifactorService = new MultifactorService(apiKey, apiSecret, callbackUrl);
services.AddSingleton(multifactorService);

.net core . , . JWT — Bearer . , JWT HTTP "Authorization: Bearer", .NET Core , , .


.net JWT Bearer


services
    .AddAuthentication(x =>
    {
        x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
        x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
    })
    .AddJwtBearer(x =>
    {
        x.RequireHttpsMetadata = true;
        x.SaveToken = true;
        x.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuerSigningKey = true,
            IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(apiSecret)), //signature validation key
            ValidateIssuer = true,
            ValidIssuer = "https://access.multifactor.ru",
            ValidateAudience = false,
            NameClaimType = ClaimTypes.NameIdentifier
        };
    });

:


  • ValidateIssuerSigningKey —
  • IssuerSigningKey ApiSecret API
  • ValidateIssuer —
  • NameClaimType

Middleware


Configure , JWT Bearer


app.Use(async (context, next) =>
    {
        var token = context.Request.Cookies["jwt"];
        if (!string.IsNullOrEmpty(token))
        {
            context.Request.Headers.Add("Authorization", "Bearer " + token);
        }

        await next();
    });


//redirect to /account/login when unauthorized
    app.UseStatusCodePages(async context => {
        var response = context.HttpContext.Response;

        if (response.StatusCode == (int)HttpStatusCode.Unauthorized)
        {
            response.Redirect("/account/login");
        }
    });

AccountController


, . , AccountController, .


API multifactor.ru


private MultifactorService _multifactorService;


[HttpPost("/account/login")]
public async Task<IActionResult> Login([Required]string login, [Required]string password)
{
    if (ModelState.IsValid)
    {
        // identity provider      
        var isValidUser = _identityService.ValidateUser(login, password, out string role);

        if (isValidUser)
        {
            var claims = new Dictionary<string, string> //         ,       
            {
                { "Role", role }
            };

            var url = await _multifactorService.GetAccessPage(login, claims);
            return RedirectPermanent(url);
        }
    }

    return View();
}


[HttpPost("/account/mfa")]
public IActionResult MultifactorCallback(string accessToken)
{
    //         
    Response.Cookies.Append("jwt", accessToken);
    return LocalRedirect("/");
}

, , Logout,


[HttpGet("/account/logout")]
public IActionResult Logout()
{
    Response.Cookies.Delete("jwt");
    return Redirect("/");
}

يتوفر مسودة عمل مع رمز من المقالة على GitHub . لفهم أعمق لكيفية عمل المصادقة المستندة إلى ClaimsIdentity في ASP.NET Core ، راجع هذه المقالة الرائعة .

Source: https://habr.com/ru/post/undefined/


All Articles