#authentication #asp.net-mvc-5
#аутентификация #asp.net-mvc-5
Вопрос:
Я унаследовал устаревшую библиотеку dll framework с ее собственными методами для входа в систему и просмотра того, к каким «группам» принадлежит пользователь. Это довольно типичный домашний модуль безопасности. Вы общаетесь с ней с помощью устаревших методов dll. Она не использует и не взаимодействует ни с одной из современных.Сетевая платформа аутентификации / авторизации.
Я создал приложение MVC5 и получил множество шаблонов, включая AccountController
и я полностью считаю, что лучше всего использовать все это, потому что это «кодирование с учетом», а не «против зерна».
Я хочу внести минимальные изменения в шаблонный код проекта MVC, чтобы он получал ответы из устаревшей библиотеки dll. Как только у него появятся эти ответы на аутентификацию / авторизацию, я хочу, чтобы он продолжал, как если бы он получил эти ответы от .Net framework.
Использовать только сценарий входа в систему в качестве примера,
Вот данный шаблонный метод внутри AccountController
:
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
if (!ModelState.IsValid)
{
return View(model);
}
//SignInStatus result = Login(model.Email, model.Password);
// This doesn't count login failures towards account lockout
// To enable password failures to trigger account lockout, change to shouldLockout: true
var result = await SignInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, shouldLockout: false);
switch (result)
{
case SignInStatus.Success:
return RedirectToLocal(returnUrl);
case SignInStatus.LockedOut:
return View("Lockout");
case SignInStatus.RequiresVerification:
return RedirectToAction("SendCode", new { ReturnUrl = returnUrl, RememberMe = model.RememberMe });
case SignInStatus.Failure:
default:
ModelState.AddModelError("", "Invalid login attempt.");
return View(model);
}
}
и вот моя процедура получения ответа «достаточно похожего для сопоставления» из моей устаревшей библиотеки dll:
private SignInStatus Login(string userId, string password)
{
string SoftwareLicenses = "blahblahblah";
try
{
UserCredentials = UserProfileType.Login(userId, password, 0, SoftwareLicenses);
return SignInStatus.Success;
}
catch (AtlasBusinessRuleException abrex)
{
switch (abrex.Message)
{
case "Invalid Login Name entered":
return SignInStatus.Failure;
case "Invalid Password entered":
return SignInStatus.Failure;
case "Your password has expired. Please change the password.":
return SignInStatus.LockedOut;
default:
return SignInStatus.Failure;
}
}
}
Видите ли, если я просто раскомментирую свой вызов моей подпрограммы в стандартной процедуре, этого недостаточно, чтобы включить все .Осведомленность Net Framework о том, что я вошел в систему и кто я такой и т.д. Я полностью понимаю, что роли разные, но прямо сейчас я соглашусь на то, чтобы просто войти в систему и заставить веб-сайт действовать так, как будто я использовал для этого его код.
Обновить
Вероятно, это как-то связано с ApplicationUserManager
и ApplicationSignInManager
тем, что он создает в IdentityConfig
файле в App_Start
.
Ответ №1:
Я использовал новую методологию, чтобы получить ответ здесь. Способ, которым я пришел к ответу, более важен, чем сам ответ.
Методология поиска ответа
- Найдите исходный код .Net Framework. В этом случае это было
Microsoft.AspNet.Identity.Core
иMicrosoft.AspNet.Identity.Owin
- Найдите классы, являющиеся «черным ящиком», с которым я хочу взаимодействовать. В этом случае это было
UserManager
иSignInManager
- Найдите метод ввода «черного ящика», с которым я хочу взаимодействовать. В данном случае это был
SignInManager.PasswordSignInAsync()
который вызывается вAccountController.Login(LoginViewModel model, string returnUrl)
- Скопируйте метод из исходного кода и вставьте его в подкласс boilerplate. В данном случае это было
ApplicationSignInManager
. Превратите метод в переопределяемый, изменивvirtual
ключевое слово наoverride
. - Найдите и закомментируйте любой код, который несовместим с моей внутренней библиотекой dll, или просто не будет работать, или не применим к моему приложению. Не слишком комментируйте и ничего не предполагайте. Комментарий консервативен.
- Если требуется функциональность, которая может быть предоставлена моей внутренней библиотекой dll, предоставьте ее с помощью моей внутренней библиотеки dll.
- Если этот «метод черного ящика» (который мы только что сделали белым ящиком) вызывает еще какие-либо темные методы, повторите шаги 4-6 для этого метода, и так далее, рекурсивно. Вы можете сделать это с отладкой или без нее. Я использовал отладку, потому что это было проще, чем доверять своим глазам, чтобы следить за ходом кода.
Результат использования этой методологии
ApplicationSignInManager.cs (переопределил 2 метода, закомментировал код, который мог бы сломаться)
using System.Threading.Tasks;
using Microsoft.AspNet.Identity.Owin;
using Microsoft.Owin;
using Microsoft.Owin.Security;
using AACOMvc.Models;
using System.Data.Entity.Utilities;
using Microsoft.AspNet.Identity;
using System;
namespace AAAMvc
{
// Configure the application sign-in manager which is used in this application.
public class ApplicationSignInManager : SignInManager<ApplicationUser, string>
{
public ApplicationSignInManager(ApplicationUserManager userManager, IAuthenticationManager authenticationManager)
: base(userManager, authenticationManager)
{
}
public override async Task<SignInStatus> PasswordSignInAsync(string userName, string password, bool isPersistent, bool shouldLockout)
{
if (UserManager == null)
{
return SignInStatus.Failure;
}
var user = await UserManager.FindByNameAsync(userName).WithCurrentCulture();
if (user == null)
{
return SignInStatus.Failure;
}
//if (await UserManager.IsLockedOutAsync(user.Id).WithCurrentCulture())
//{
// return SignInStatus.LockedOut;
//}
if (await UserManager.CheckPasswordAsync(user, password).WithCurrentCulture())
{
//await UserManager.ResetAccessFailedCountAsync(user.Id).WithCurrentCulture();
return await SignInOrTwoFactor(user, isPersistent).WithCurrentCulture();
}
//if (shouldLockout)
//{
// // If lockout is requested, increment access failed count which might lock out the user
// await UserManager.AccessFailedAsync(user.Id).WithCurrentCulture();
// if (await UserManager.IsLockedOutAsync(user.Id).WithCurrentCulture())
// {
// return SignInStatus.LockedOut;
// }
//}
return SignInStatus.Failure;
}
private async Task<SignInStatus> SignInOrTwoFactor(ApplicationUser user, bool isPersistent)
{
var id = Convert.ToString(user.Id);
//if (await UserManager.GetTwoFactorEnabledAsync(user.Id).WithCurrentCulture()
// amp;amp; (await UserManager.GetValidTwoFactorProvidersAsync(user.Id).WithCurrentCulture()).Count > 0
// amp;amp; !await AuthenticationManager.TwoFactorBrowserRememberedAsync(id).WithCurrentCulture())
//{
// var identity = new ClaimsIdentity(DefaultAuthenticationTypes.TwoFactorCookie);
// identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, id));
// AuthenticationManager.SignIn(identity);
// return SignInStatus.RequiresVerification;
//}
await SignInAsync(user, isPersistent, false).WithCurrentCulture();
return SignInStatus.Success;
}
public static ApplicationSignInManager Create(IdentityFactoryOptions<ApplicationSignInManager> options, IOwinContext context)
{
return new ApplicationSignInManager(context.GetUserManager<ApplicationUserManager>(), context.Authentication);
}
}
}
ApplicationUserManager.cs (переопределил 4 метода, прокомментировал код, который мог бы сломаться, и добавил свой код, чтобы заставить его работать так, как работает устаревшая библиотека dll)
using System;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.EntityFramework;
using Microsoft.AspNet.Identity.Owin;
using Microsoft.Owin;
using AACOMvc.Models;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Linq;
using HerculesBusinessModel;
using System.Security.Claims;
using System.Data.Entity.Utilities;
namespace AAAMvc
{
// Configure the application user manager used in this application. UserManager is defined in ASP.NET Identity and is used by the application.
public class ApplicationUserManager : UserManager<ApplicationUser>
{
public ApplicationUserManager(IUserStore<ApplicationUser> store)
: base(store)
{
}
public static UserCredentialsType UserCredentials
{
get
{
return (UserCredentialsType)System.Web.HttpContext.Current.Session["UserProfile"];
}
set
{
System.Web.HttpContext.Current.Session["UserProfile"] = value;
}
}
public override async Task<bool> CheckPasswordAsync(ApplicationUser user, string password)
{
if (user == null)
{
return false;
}
await Task.Delay(0);
string SoftwareLicenses = "blahblahblah";
try
{
UserCredentials = UserProfileType.Login(user.UserName, password, 0, SoftwareLicenses);
return true;
}
catch (HerculesBusinessRuleException ex)
{
}
return false;
}
public override async Task<ClaimsIdentity> CreateIdentityAsync(ApplicationUser user, string authenticationType)
{
//ThrowIfDisposed();
if (user == null)
{
throw new ArgumentNullException("user");
}
const string IdentityProviderClaimType =
"http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider";
const string DefaultIdentityProviderClaimValue = "ASP.NET Identity";
var id = new ClaimsIdentity(authenticationType, ClaimsIdentity.DefaultNameClaimType, ClaimsIdentity.DefaultRoleClaimType);
id.AddClaim(new Claim(ClaimTypes.NameIdentifier, user.Id, ClaimValueTypes.String));
id.AddClaim(new Claim(ClaimsIdentity.DefaultNameClaimType, user.UserName, ClaimValueTypes.String));
id.AddClaim(new Claim(IdentityProviderClaimType, DefaultIdentityProviderClaimValue, ClaimValueTypes.String));
if (SupportsUserRole)
{
IList<string> roles = await GetRolesAsync(user.Id).WithCurrentCulture();
foreach (string roleName in roles)
{
id.AddClaim(new Claim(ClaimsIdentity.DefaultRoleClaimType, roleName, ClaimValueTypes.String));
}
}
if (SupportsUserClaim)
{
//id.AddClaims(await GetClaimsAsync(user.Id).WithCurrentCulture());
}
return id;
}
ApplicationUser user;
public override async Task<ApplicationUser> FindByNameAsync(string userName)
{
await Task.Delay(0);
var profile = UserProfileType.FetchUserProfileForLogin(userName);
if (profile != null)
{
user = new ApplicationUser()
{
UserName = profile.LoginName,
Id = profile.Id.ToString()
};
foreach (decimal groupId in profile.UserGroups)
{
user.Roles.Add(new IdentityUserRole() { UserId = profile.Id.ToString(), RoleId = groupId.ToString() });
}
}
return user;
}
public override async Task<IList<string>> GetRolesAsync(string userId)
{
////ThrowIfDisposed();
//var userRoleStore = GetUserRoleStore();
//var user = await FindByIdAsync(userId).WithCurrentCulture();
//if (user == null)
//{
// throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, Resources.UserIdNotFound,
// userId));
//}
//return await userRoleStore.GetRolesAsync(user).WithCurrentCulture();
await Task.Delay(0);
return user.Roles.Select(r => r.RoleId).ToList();
}
}
}
Заключение
Я был удивлен и очень доволен, что несколько роботизированный процесс может дать ответ. Но я был еще более доволен тем, что использование этого процесса научило меня большему о странном новом пространстве имен ( Microsoft.AspNet.Identity
), чем я мог бы узнать за часы чтения или поиска в Google. Возможно, это не идеально, но я могу использовать тот же процесс, чтобы улучшить то, что у меня есть здесь.