تصف المقالة طريقة لربط المصادقة متعددة العوامل لموقع يعمل على النظام الأساسي .net باستخدام آليات التفويض المضمنة.
بضع كلمات لماذا هناك حاجة إلى المصادقة متعددة العوامل على الإطلاق:
- سلامة
- السلامة مرة أخرى
- السهولة أو الراحة
نعم ، النقطة الأخيرة ليست خطأ. عامل المصادقة الثاني و / أو الثالث ليس فقط إضافة إلى كلمة المرور التقليدية ، ولكن أيضًا بديل كامل. بدلاً من الرسائل القصيرة التي تحتوي على الرمز الذي يجب مقاطعته على الموقع ، فإن الأساليب الحديثة هي PUSH في برنامج المراسلة مع زر لتأكيد الإجراء أو المصادقة البيومترية باستخدام بصمة على الهاتف أو الكمبيوتر المحمول.
مبدأ التشغيل
- يطلب الموقع ويتحقق من اسم المستخدم / كلمة المرور للمستخدم
- إذا تم تحديدها بشكل صحيح ، يرسل المستخدم إلى صفحة المصادقة من خلال العامل الثاني
- بعد نجاح عملية التحقق ، يعود المستخدم إلى الموقع باستخدام رمز وصول ويقوم بتسجيل الدخول ، أي الحصول على إذن لعرض الجزء المغلق من الموقع.
رمز وصول
— JWT (JSON Web Token). . , , JSON . : , . base64-url. ,
{
"typ": "JWT",
"alg": "HS256"
}
, , , , . : , claims . , iss, aud, sub .
{
"iss": "https://access.multifactor.ru",
"aud": "https://example.com",
"sub": "user@example.com",
"jti": "RxMEyo9",
"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. :
- .
appsettings.json API multifactor.ru
"Multifactor": {
"ApiKey": "",
"ApiSecret": "",
"CallbackUrl": "https://localhost:44300/account/mfa"
}
- ApiKey ApiSecret
- CallbackUrl —
API
API multifactor.ru
public class MultifactorService
{
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,
Callback = new
{
Action = _callbackUrl,
Target = "_self"
},
Claims = claims
});
var payLoad = Encoding.UTF8.GetBytes(request);
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
var multifactorSection = Configuration.GetSection("Multifactor");
var apiKey = multifactorSection["ApiKey"];
var apiSecret = multifactorSection["ApiSecret"];
var callbackUrl = multifactorSection["CallbackUrl"];
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)),
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();
});
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)
{
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 ، راجع هذه المقالة الرائعة .