webentwicklung-frage-antwort-db.com.de

Benutzerdefinierte Identität mit MVC5 und OWIN

Ich versuche, dem ApplicationUser benutzerdefinierte Eigenschaften für eine Website hinzuzufügen, die MVC5- und OWIN-Authentifizierung verwendet. Ich habe https://stackoverflow.com/a/10524305/264607 gelesen, und ich mag die Integration mit dem Basis-Controller für den einfachen Zugriff auf die neuen Eigenschaften. Mein Problem ist, dass ich, wenn ich die HTTPContext.Current.User -Eigenschaft auf meinen neuen IPrincipal setze, einen Nullreferenzfehler erhalte:

[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

Hier ist mein Code:

    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; }
}

Ich habe nicht viel in der Dokumentation gefunden, wie das geht, ich habe diese Artikel gelesen:

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

In den Kommentaren im zweiten Link wurde darauf hingewiesen, dass möglicherweise Ansprüche verwendet werden ( http://msdn.Microsoft.com/de-de/library/ms734687.aspx?cs-save-lang=1&cs-lang=csharp ). Der Artikel, mit dem verlinkt wird, zeigt jedoch nicht, wie Sie diese zu einer IPrincipal hinzufügen (was HttpContext.Current.User ist) oder wo Sie sie in der Pipeline zu einer ClaimsIdentity hinzufügen sollen (was die konkrete Klasse ist, die User ist). Ich neige dazu, Ansprüche zu verwenden, aber ich muss wissen, wo ich dem Benutzer diese neuen Ansprüche hinzufügen kann. 

Auch wenn die Behauptung der richtige Weg ist, bin ich neugierig, was ich mit meinem benutzerdefinierten IPrincipal falsch mache, da ich anscheinend alles umgesetzt habe, was erforderlich ist.

20
BlackICE

Ich kann mit Claims-basierter Sicherheit etwas zur Arbeit bringen. Wenn Sie also etwas schnell erledigen möchten, haben Sie hier das, was ich momentan habe:

Fügen Sie im Anmeldeprozess in der Variable AccountController (meins innerhalb der Methode SignInAsync) einen neuen Anspruch zu der von UserManager erstellten Identität hinzu:

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);
}

Dann habe ich in meinen Basis-Controller-Klassen einfach eine Eigenschaft hinzugefügt:

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;
    }
}

Dieser Link war hilfreich für das Wissen von Ansprüchen: http://msdn.Microsoft.com/de-de/library/ms734687.aspx?cs-save-lang=1&cs-lang=csharp#code-snippet-1


Update für das Problem mit IPrincipal

Ich habe es bis zur Identity-Eigenschaft gesucht. Das Problem war, dass ich einen Standardkonstruktor für die PatientPortalPrincipal-Klasse bereitstellte, der die Identity-Eigenschaft nicht festlegte. Am Ende habe ich den Standardkonstruktor entfernt und den korrekten Konstruktor aus Application_PostAuthenticateRequest aufgerufen. Der aktualisierte Code ist unten

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;
    }
}

So funktioniert das Ganze!

17
BlackICE

Sie erhalten eine Ausnahmebedingung, da HttpContext.Current.User.Identity.IsAuthenticated zum Zeitpunkt der Überprüfung false zurückgibt (HttpContext.Current.Request.IsAuthenticated).

Wenn Sie die if (HttpContext.Current.User.Identity.IsAuthenticated)-Anweisung entfernen, funktioniert sie einwandfrei (zumindest dieser Teil des Codes).

Ich habe eine einfache Sache so ausprobiert:

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; }
}

Global.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();
}

Und das funktioniert gut. Wenn nicht dieser Code:

userManager = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(new ApplicationDbContext()));

ApplicationUser user = userManager.FindByName(HttpContext.Current.User.Identity.Name);

gibt kein gültiges (benutzerdefiniertes) Benutzerobjekt zurück, das Problem liegt in der if()-Anweisung.

Ihr Update sieht gut aus, und wenn Sie glücklich sind, Daten als Ansprüche in einem Cookie zu speichern, können Sie damit weitermachen, obwohl ich persönlich den try {} catch-Block dort hasse. 

Was ich stattdessen mache ist folgendes:

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; }
}

Ich dekoriere die Basis-Controller-Klasse mit einem benutzerdefinierten Attribut.

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);
        }
    }
}

Und 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));
    }

Dann greife ich auf den WorkContext wie folgt zu:

public class DashboardController : BaseController
{
    public ActionResult Index()
    {
        ViewBag.User = WorkContext.CurrentUser;

        return View();
    }
}

Ich verwende Ninjects Abhängigkeitslöser, um authService und cacheManager aufzulösen, aber Sie können das Zwischenspeichern überspringen und authService durch ASP.NET-Identität UserManager ersetzen, glaube ich.

Ich wollte auch Kredit geben, wo es fällig ist, da die WorkContext-Klasse stark vom NugetGallery-Projekt inspiriert ist.

4
LukeP

Ich wette, dass HttpContext.Current.User null ist. Also stattdessen:

if (HttpContext.Current.User.Identity.IsAuthenticated)

sie können dies versuchen:

if (HttpContext.Current.Request.IsAuthenticated)
3
Brock Allen

Ich habe den gleichen Fehler gehabt. 

Mein Problem war, dass ich bei anonymen Benutzern die IP-Adresse nicht auf IPrincipal gesetzt habe. Ich habe dies nur gemacht, wenn sich Benutzer mit Benutzernamen angemeldet haben. Ansonsten war IIdentity null.

Meine Lösung bestand darin, immer IIdentity festzulegen. Wenn der Benutzer nicht authentifiziert ist (anonymer Benutzer), wird IIdentity.IsAuthenticated auf "false" gesetzt. Ansonsten stimmt es.

Mein Code:

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
FrenkyB