使用MVC5和OWIN的自定义标识

Bla*_*ICE 22 c# iprincipal claims-based-identity owin asp.net-mvc-5

我尝试使用MVC5和OWIN身份验证为Web站点的ApplicationUser添加自定义属性.我已经阅读了/sf/answers/736701381/,我喜欢它如何与基本控制器集成,以便轻松访问新属性.我的问题是,当我将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
Run Code Online (Sandbox Code Playgroud)

这是我的代码:

    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; }
}
Run Code Online (Sandbox Code Playgroud)

我没有找到关于如何做到这一点的文档的方式,我读过这些文章:

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://msdn.microsoft.com/en-us/library/ms734687.aspx?cs-save-lang=1&cs-lang=csharp),但文章链接没有说明如何将它们添加到IPrincipal(这是什么HttpContext.Current.User),或者在管道中你应该将它们添加到a ClaimsIdentity(这是具体的类User).我倾向于使用声明,但我需要知道将这些新声明添加到用户的位置.

即使声明是要走的路,我也很好奇我的自定义IPrincipal我做错了什么,因为我似乎已经实现了它所需要的一切.

Bla*_*ICE 18

我可以使用Claims基于安全性的东西来工作,所以如果你想快速完成某些事情,我现在所拥有的是:

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);
}
Run Code Online (Sandbox Code Playgroud)

然后在我的基本控制器类中,我只添加了一个属性:

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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


更新IPrincipal的问题

我追踪它到了Identity酒店.问题是我PatientPortalPrincipal在没有设置Identity属性的类上提供了一个默认构造函数.我最终做的是删除默认构造函数并从内部调用正确的构造函数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;
    }
}
Run Code Online (Sandbox Code Playgroud)

这使得整个事情都有效!


Luk*_*keP 5

您会收到一个异常,因为HttpContext.Current.User.Identity.IsAuthenticated在检查时返回false(HttpContext.Current.Request.IsAuthenticated)。

如果删除该if (HttpContext.Current.User.Identity.IsAuthenticated)语句,它将正常工作(至少在这部分代码中)。

我已经尝试过像这样的简单事情:

BaseController.cs

public abstract class BaseController : Controller
{
    protected virtual new CustomPrincipal User
    {
        get { return HttpContext.User as CustomPrincipal; }
    }
}
Run Code Online (Sandbox Code Playgroud)

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; }
}
Run Code Online (Sandbox Code Playgroud)

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;
}
Run Code Online (Sandbox Code Playgroud)

HomeController.cs

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

    return View();
}
Run Code Online (Sandbox Code Playgroud)

这很好。因此,除非这段代码:

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

ApplicationUser user = userManager.FindByName(HttpContext.Current.User.Identity.Name);
Run Code Online (Sandbox Code Playgroud)

没有返回有效的(自定义)用户对象,则问题出在if()语句上。

您的更新看起来不错,并且如果您乐意将数据作为声明存储在cookie中,则可以使用它,尽管我个人讨厌try {}那里的catch块。

我要做的是:

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; }
}
Run Code Online (Sandbox Code Playgroud)

我用自定义属性装饰基本控制器类。

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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

和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));
    }
Run Code Online (Sandbox Code Playgroud)

然后,我像这样访问WorkContext:

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

        return View();
    }
}
Run Code Online (Sandbox Code Playgroud)

我正在使用Ninject的Dependency Resolver进行解析authServicecacheManager但是您可以跳过缓存,并用UserManager我相信的ASP.NET Identity替换authService 。

我还想归功于它的应有之处,因为WorkContext类受NugetGallery项目的启发很大。