Der Artikel beschreibt eine Methode zum Verbinden der Multifaktorauthentifizierung für eine Site, die auf der .net-Kernplattform ausgeführt wird, mithilfe integrierter Autorisierungsmechanismen.
Ein paar Worte, warum überhaupt eine Multifaktorauthentifizierung erforderlich ist:
- Sicherheit
- Wieder Sicherheit
- Bequemlichkeit
Ja, der letzte Punkt ist kein Fehler. Der zweite und / oder dritte Authentifizierungsfaktor ist nicht nur eine Ergänzung zum herkömmlichen Passwort, sondern auch ein vollständiger Ersatz. Anstelle von SMS mit dem Code, der auf der Site unterbrochen werden muss, werden moderne Methoden im Messenger mit einer Taste gedrückt, um die Aktion oder die biometrische Authentifizierung mithilfe eines Fingerabdrucks auf dem Telefon oder Laptop zu bestätigen.
Arbeitsprinzip
- Die Site fordert den Benutzernamen / das Passwort des Benutzers an und überprüft ihn
- Wenn sie korrekt angegeben sind, wird der Benutzer über den zweiten Faktor zur Authentifizierungsseite gesendet
- Nach erfolgreicher Überprüfung kehrt der Benutzer mit einem Zugriffstoken zur Site zurück und meldet sich an, erhält also die Berechtigung zum Anzeigen des geschlossenen Teils der Site.
Zugangstoken
— 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("/");
}
Ein Arbeitsentwurf mit dem Code aus dem Artikel ist auf GitHub verfügbar . Weitere Informationen zur Funktionsweise der ClaimsIdentity-basierten Authentifizierung in ASP.NET Core finden Sie in diesem großartigen Artikel .