简体   繁体   English

使用MVC5和OWIN的自定义标识

[英]Custom Identity using MVC5 and OWIN

I trying to add custom properties to the ApplicationUser for a web site using MVC5 and OWIN authentication. 我尝试使用MVC5和OWIN身份验证为Web站点的ApplicationUser添加自定义属性。 I've read https://stackoverflow.com/a/10524305/264607 and I like how it integrates with the base controller for easy access to the new properties. 我已经阅读了https://stackoverflow.com/a/10524305/264607 ,我喜欢它如何与基本控制器集成,以便轻松访问新属性。 My issue is that when I set the HTTPContext.Current.User property to my new IPrincipal I get a null reference error: 我的问题是,当我将HTTPContext.Current.User属性设置为我的新IPrincipal时,我得到一个空引用错误:

[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

Here is my 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; }
}

I haven't found much in the way of documentation on how to do this, I've read these articles: 我没有找到关于如何做到这一点的文档的方式,我读过这些文章:

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/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 http://blogs.msdn.com/b/webdev/archive/2013/07/03/understanding-owin-forms-authentication-in-mvc-5.aspx

The comments in the second link pointed me to perhaps using claims ( http://msdn.microsoft.com/en-us/library/ms734687.aspx?cs-save-lang=1&cs-lang=csharp ) , but the article linked to doesn't show how to add those to an IPrincipal (which is what HttpContext.Current.User is), or where in the pipeline you're supposed to add them to a ClaimsIdentity (which is the concrete class the User is). 第二个链接中的注释指向我可能使用声明( http://msdn.microsoft.com/en-us/library/ms734687.aspx?cs-save-lang=1&cs-lang=csharp ),但文章链接没有说明如何将这些添加到IPrincipal (这是HttpContext.Current.User是什么),或者在管道中你应该将它们添加到ClaimsIdentity (这是User的具体类)。 I'm leaning towards using claims, but I need to know where to add these new claims to the user. 我倾向于使用声明,但我需要知道将这些新声明添加到用户的位置。

Even if claims are the way to go, I'm curious as to what I'm doing wrong with my custom IPrincipal, as I seem to have implemented everything it requires. 即使声明是要走的路,我也很好奇我的自定义IPrincipal我做错了什么,因为我似乎已经实现了它所需要的一切。

I can get something to work using Claims based security, so if you're looking to get something done quickly here is what I have at the moment: 我可以使用基于Claims的安全性来处理某些事情,所以如果你想快速完成某些事情,我现在所拥有的是:

In the login process in the AccountController (mine is within SignInAsync method), add a new claim to the identity created by UserManager : AccountController的登录过程中(我的是SignInAsync方法),向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);
}

Then in my base controller classes I simply added a property: 然后在我的基本控制器类中,我只添加了一个属性:

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

This link was helpful for claims knowledge: http://msdn.microsoft.com/en-us/library/ms734687.aspx?cs-save-lang=1&cs-lang=csharp#code-snippet-1 此链接对索赔知识很有帮助: http//msdn.microsoft.com/en-us/library/ms734687.aspx?cs-save-lang = 1&ct-lang = csharp#code-snippet-1


Update for the issue with IPrincipal 更新IPrincipal的问题

I tracked it down to the Identity property. 我将其跟踪到Identity属性。 The issue was that I was providing a default constructor on the PatientPortalPrincipal class that was not setting the Identity property. 问题是我在PatientPortalPrincipal类上提供了一个未设置Identity属性的默认构造函数。 What I ended up doing was removing the default constructor and calling the correct constructor from within Application_PostAuthenticateRequest , updated code is below 我最终做的是删除默认构造函数并从Application_PostAuthenticateRequest调用正确的构造函数,更新的代码如下

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

That makes the whole thing work! 这使得整个事情都有效!

You're getting an exception because HttpContext.Current.User.Identity.IsAuthenticated returns false at the point of check (so does HttpContext.Current.Request.IsAuthenticated ). 你得到一个例外,因为HttpContext.Current.User.Identity.IsAuthenticated在检查点返回false( HttpContext.Current.Request.IsAuthenticated也是如此)。

If you remove the if (HttpContext.Current.User.Identity.IsAuthenticated) statement it will work fine (at least this part of code). 如果删除if (HttpContext.Current.User.Identity.IsAuthenticated)语句,它将正常工作(至少这部分代码)。

I've tried a simple thing like this: 我尝试过这样一个简单的事情:

BaseController.cs BaseController.cs

public abstract class BaseController : Controller
{
    protected virtual new CustomPrincipal User
    {
        get { return HttpContext.User as CustomPrincipal; }
    }
}

CustomPrincipal.cs 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 的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 HomeController.cs

public ActionResult Index()
{
    ViewBag.BirthDate = User.BirthDate;
    ViewBag.InvitationCode = User.InvitationCode;
    ViewBag.PatientNumber = User.PatientNumber;

    return View();
}

And this is working fine. 这工作正常。 So unless this code: 所以除非这个代码:

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

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

is not returning a valid (custom) user object, the problem is with the if() statement. 没有返回有效的(自定义)用户对象,问题在于if()语句。

Your update looks fine, and if you're happy to store data as claims in a cookie you can go with it, although I personally hate the try {} catch block there. 您的更新看起来很好,如果您乐意将数据存储为cookie中的声明,您可以使用它,尽管我个人讨厌那里的try {} catch块。

What I do instead is this: 我做的是这样的:

BaseController.cs 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; }
}

I decorate the base controller class with a custom attribute. 我用自定义属性修饰基本控制器类。

AuthorizeExAttribute.cs: 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);
        }
    }
}

And WorkContext.cs: 和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 then access the WorkContext like this: 然后我像这样访问WorkContext:

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

        return View();
    }
}

I'm using Ninject's Dependency Resolver to resolve authService and cacheManager but you can skip caching and replace authService with ASP.NET Identity UserManager I believe. 我正在使用Ninject的Dependency Resolver来解析authServicecacheManager但我相信你可以跳过缓存并用ASP.NET Identity UserManager替换authService。

I also wanted to give credit where it's due as the WorkContext class is heavily inspired by NugetGallery project. 由于WorkContext课程受到NugetGallery项目的极大启发,我还想给予应有的信任。

I bet HttpContext.Current.User is null. 我敢打赌HttpContext.Current.User为null。 So instead of this: 所以不是这样的:

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

you can try this: 你可以试试这个:

if (HttpContext.Current.Request.IsAuthenticated)

I've had the same error. 我有同样的错误。

My problem was that with anonymous users I wasn't setting the IIdentity on IPrincipal. 我的问题是,对于匿名用户,我没有在IPrincipal上设置IIdentity。 I did this only when users logged in with user name. 我只在用户使用用户名登录时才这样做。 Otherwise, IIdentity was null. 否则,IIdentity无效。

My solution was to always set IIdentity. 我的解决方案是始终设置IIdentity。 If user is not authenticated (anonymous user) then IIdentity.IsAuthenticated is set to false. 如果用户未经过身份验证(匿名用户),则IIdentity.IsAuthenticated将设置为false。 Otherwise, true. 否则,是的。

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM