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:

Http://blogs.msdn.com/b/webdev/archive/2013/10/16/customizing-profile-information-in-asp-net-identity-in-vs-2013-templates.aspx

Http://blogs.msdn.com/b/webdev/archive/2013/07/03/understanding-owin-forms-authentication-in-mvc-5.aspx

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.

Author: Community, 2014-02-10

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 klasy PatientPortalPrincipal, 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!
 16
Author: BlackICE,
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.

 4
Author: LukeP,
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)
 3
Author: Brock Allen,
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 }
     };
}
 0
Author: FrenkyB,
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