Custom Identity using MVC5 and OWIN
Próbuję dodać własne właściwości do aplikacji dla strony internetowej przy użyciu uwierzytelniania MVC5 i OWIN. Przeczytałem https://stackoverflow.com/a/10524305/264607 i podoba mi się, jak integruje się z kontrolerem bazowym dla łatwego dostępu do nowych właściwości. Mój problem polega na tym, że po ustawieniu HTTPContext.Aktualne.Właściwość użytkownika do mojego nowego IPrincipal dostaję null reference error:
[NullReferenceException: Object reference not set to an instance of an object.]
System.Web.Security.UrlAuthorizationModule.OnEnter(Object source, EventArgs eventArgs) +127
System.Web.SyncEventExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +136
System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +69
Oto Mój kod:
protected void Application_PostAuthenticateRequest(Object sender, EventArgs e)
{
if (HttpContext.Current.User.Identity.IsAuthenticated)
{
userManager = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(new ApplicationDbContext()));
ApplicationUser user = userManager.FindByName(HttpContext.Current.User.Identity.Name);
PatientPortalPrincipal newUser = new PatientPortalPrincipal();
newUser.BirthDate = user.BirthDate;
newUser.InvitationCode = user.InvitationCode;
newUser.PatientNumber = user.PatientNumber;
//Claim cPatient = new Claim(typeof(PatientPortalPrincipal).ToString(), );
HttpContext.Current.User = newUser;
}
}
public class PatientPortalPrincipal : ClaimsPrincipal, IPatientPortalPrincipal
{
public PatientPortalPrincipal(ApplicationUser user)
{
Identity = new GenericIdentity(user.UserName);
BirthDate = user.BirthDate;
InvitationCode = user.InvitationCode;
}
public PatientPortalPrincipal() { }
public new bool IsInRole(string role)
{
if(!string.IsNullOrWhiteSpace(role))
return Role.ToString().Equals(role);
return false;
}
public new IIdentity Identity { get; private set; }
public WindowsBuiltInRole Role { get; set; }
public DateTime BirthDate { get; set; }
public string InvitationCode { get; set; }
public string PatientNumber { get; set; }
}
public interface IPatientPortalPrincipal : IPrincipal
{
WindowsBuiltInRole Role { get; set; }
DateTime BirthDate { get; set; }
string InvitationCode { get; set; }
string PatientNumber { get; set; }
}
Nie znalazłem wiele w drodze dokumentacji na jak to zrobić, czytałem te artykuły:
Komentarze w drugim linku wskazywały mi być może na używanie twierdzeń ( http://msdn.microsoft.com/en-us/library/ms734687.aspx?cs-save-lang=1&cs-lang=csharp ), ale artykuł, do którego linkujemy, nie pokazuje, jak dodać je do IPrincipal
(czyli tego, czym jest HttpContext.Current.User
), Ani gdzie w potoku należy dodać je do ClaimsIdentity
(czyli konkretnej klasy User
). Skłaniam się ku korzystaniu z roszczeń, ale muszę wiedzieć, gdzie dodać te nowe roszczenia do użytkownika.
Nawet jeśli roszczenia są drogą do zrobienia, jestem ciekaw, co robię źle z moim niestandardowym IPrincipal, ponieważ wydaje się, że zaimplementowałem wszystko, czego wymaga.
4 answers
Mogę uzyskać coś do pracy za pomocą Claims
zabezpieczeń opartych, więc jeśli chcesz zrobić coś szybko tutaj jest to, co mam w tej chwili:
W procesie logowania w AccountController
(moja jest w metodzie SignInAsync
), Dodaj nowe zastrzeżenie do tożsamości utworzonej przez UserManager
:
private async Task SignInAsync(ApplicationUser user, bool isPersistent)
{
AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie);
var identity = await UserManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie);
identity.AddClaim(new Claim("PatientNumber", user.PatientNumber)); //This is what I added
AuthenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = isPersistent }, identity);
}
Następnie w klasach kontrolerów bazowych po prostu dodałem właściwość:
private string _patientNumber;
public string PatientNumber
{
get
{
if (string.IsNullOrWhiteSpace(_patientNumber))
{
try
{
var cp = ClaimsPrincipal.Current.Identities.First();
var patientNumber = cp.Claims.First(c => c.Type == "PatientNumber").Value;
_patientNumber = patientNumber;
}
catch (Exception)
{
}
}
return _patientNumber;
}
}
Ten link był pomocny dla wiedzy o twierdzeniach: http://msdn.microsoft.com/en-us/library/ms734687.aspx?cs-save-lang=1&cs-lang=csharp#code-snippet-1
Aktualizacja problemu z IPrincipal
Wyśledziłem go do posiadłości. Problem polegał na tym, że dostarczałem domyślny konstruktor klasyPatientPortalPrincipal
, który nie ustawiał właściwości Identity. Skończyło się na usunięciu domyślnego konstruktora i wywołaniu WŁAŚCIWEGO konstruktora z wewnątrz Application_PostAuthenticateRequest
, zaktualizowany kod jest poniżej
protected void Application_PostAuthenticateRequest(Object sender, EventArgs e)
{
if (HttpContext.Current.User.Identity.IsAuthenticated)
{
userManager = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(new ApplicationDbContext()));
ApplicationUser user = userManager.FindByName(HttpContext.Current.User.Identity.Name);
PatientPortalPrincipal newUser = new PatientPortalPrincipal(user);
newUser.BirthDate = user.BirthDate;
newUser.InvitationCode = user.InvitationCode;
newUser.PatientNumber = user.PatientNumber;
//Claim cPatient = new Claim(typeof(PatientPortalPrincipal).ToString(), );
HttpContext.Current.User = newUser;
}
}
To sprawia, że wszystko działa!Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-02-11 15:38:44
Dostajesz wyjątek, ponieważ HttpContext.Current.User.Identity.IsAuthenticated
zwraca false w momencie sprawdzania(podobnie jak HttpContext.Current.Request.IsAuthenticated
).
Jeśli usuniesz if (HttpContext.Current.User.Identity.IsAuthenticated)
instrukcja będzie działać poprawnie (przynajmniej ta część kodu).
Próbowałem prostej rzeczy takiej jak ta:
BaseController.cs
public abstract class BaseController : Controller
{
protected virtual new CustomPrincipal User
{
get { return HttpContext.User as CustomPrincipal; }
}
}
CustomPrincipal.cs
public class CustomPrincipal : IPrincipal
{
public IIdentity Identity { get; private set; }
public bool IsInRole(string role) { return false; }
public CustomPrincipal(string username)
{
this.Identity = new GenericIdentity(username);
}
public DateTime BirthDate { get; set; }
public string InvitationCode { get; set; }
public int PatientNumber { get; set; }
}
Globalny.asax.cs
protected void Application_PostAuthenticateRequest(Object sender, EventArgs e)
{
CustomPrincipal customUser = new CustomPrincipal(User.Identity.Name);
customUser.BirthDate = DateTime.Now;
customUser.InvitationCode = "1234567890A";
customUser.PatientNumber = 100;
HttpContext.Current.User = customUser;
}
HomeController.cs
public ActionResult Index()
{
ViewBag.BirthDate = User.BirthDate;
ViewBag.InvitationCode = User.InvitationCode;
ViewBag.PatientNumber = User.PatientNumber;
return View();
}
I to działa dobrze. Więc chyba, że ten kod:
userManager = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(new ApplicationDbContext()));
ApplicationUser user = userManager.FindByName(HttpContext.Current.User.Identity.Name);
Nie zwraca valid (custom) user object, problem dotyczy instrukcji if()
.
Twoja aktualizacja wygląda dobrze, a jeśli jesteś szczęśliwy, aby przechowywać dane jako roszczenia w pliku cookie można go z nim, choć osobiście nienawidzę try {}
złapać blok tam.
Zamiast tego robię to:
BaseController.cs
[AuthorizeEx]
public abstract partial class BaseController : Controller
{
public IOwinContext OwinContext
{
get { return HttpContext.GetOwinContext(); }
}
public new ClaimsPrincipal User
{
get { return base.User as ClaimsPrincipal; }
}
public WorkContext WorkContext { get; set; }
}
Dekoruję klasę kontrolera bazowego niestandardowym atrybutem.
AuthorizeExAttribute.cs:
public class AuthorizeExAttribute : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
Ensure.Argument.NotNull(filterContext);
base.OnAuthorization(filterContext);
IPrincipal user = filterContext.HttpContext.User;
if (user.Identity.IsAuthenticated)
{
var ctrl = filterContext.Controller as BaseController;
ctrl.WorkContext = new WorkContext(user.Identity.Name);
}
}
}
I WorkContext.cs:
public class WorkContext
{
private string _email;
private Lazy<User> currentUser;
private IAuthenticationService authService;
private ICacheManager cacheManager;
public User CurrentUser
{
get
{
var cachedUser = cacheManager.Get<User>(Constants.CacheUserKeyPrefix + this._email);
if (cachedUser != null)
{
return cachedUser;
}
else
{
var user = currentUser.Value;
cacheManager.Set(Constants.CacheUserKeyPrefix + this._email, user, 30);
return user;
}
}
}
public WorkContext(string email)
{
Ensure.Argument.NotNullOrEmpty(email);
this._email = email;
this.authService = DependencyResolver.Current.GetService<IAuthenticationService>();
this.cacheManager = DependencyResolver.Current.GetService<ICacheManager>();
this.currentUser = new Lazy<User>(() => authService.GetUserByEmail(email));
}
I wtedy uzyskaj dostęp do WorkContext w następujący sposób:
public class DashboardController : BaseController
{
public ActionResult Index()
{
ViewBag.User = WorkContext.CurrentUser;
return View();
}
}
Używam rozdzielacza zależności Ninject do rozwiązywania authService
i cacheManager
, ale możesz pominąć buforowanie i zastąpić authService ASP.NET tożsamość UserManager
wierzę.
Chciałem również przypisać to, gdzie jest to należne, ponieważ Klasa WorkContext jest mocno inspirowana projektem nugetgallery.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-02-10 22:04:11
Stawiam HttpContext.Aktualne.Użytkownik jest null. Więc zamiast tego:
if (HttpContext.Current.User.Identity.IsAuthenticated)
Możesz spróbować tego:
if (HttpContext.Current.Request.IsAuthenticated)
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-02-04 13:06:23
Miałem ten sam błąd.
Mój problem polegał na tym, że z anonimowymi użytkownikami Nie ustawiałem IIdentity na IPrincipal. Zrobiłem to tylko wtedy, gdy użytkownicy zalogowali się za pomocą nazwy użytkownika. W przeciwnym razie, nie było nic.
Moim rozwiązaniem było zawsze ustawić IIdentity. Jeżeli użytkownik nie jest uwierzytelniony (użytkownik anonimowy) to IIdentity.IsAuthenticated jest ustawione na false. Inaczej to prawda.
Mój kod:
private PrincipalCustom SetPrincipalIPAndBrowser()
{
return new PrincipalCustom
{
IP = RequestHelper.GetIPFromCurrentRequest(HttpContext.Current.Request),
Browser = RequestHelper.GetBrowserFromCurrentRequest(HttpContext.Current.Request),
/* User is not authenticated, but Identity must be set anyway. If not, error occurs */
Identity = new IdentityCustom { IsAuthenticated = false }
};
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-09-30 09:38:53