我试图使用MVC5和OWIN身份验证为网站的ApplicationUser添加自定义属性。我读过https://stackoverflow.com/a/10524305/264607我喜欢它如何与基本控制器集成,以便轻松访问新属性。我的问题是,当我将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
这是我的代码:
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; }
}
我没有发现太多关于如何做到这一点的文档,我已经阅读了以下文章:
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-保存lang=1&cslang=csharp),但链接到的文章没有显示如何将它们添加到IPrincipal
(这就是HttpContext.Current.User
),也没有显示应该在管道中的何处将它们添加至ClaimsIdentity
(这是User
的具体类)。我倾向于使用声明,但我需要知道在哪里向用户添加这些新声明。
即使索赔是可行的,我也很好奇我的自定义IPrincipal做错了什么,因为我似乎已经实现了它所需要的一切。
我可以使用基于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);
}
然后在我的基本控制器类中,我简单地添加了一个属性:
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;
}
}
此链接有助于了解索赔知识:http://msdn.microsoft.com/en-us/library/ms734687.aspx?cs-保存lang=1&cs 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;
}
}
这让整件事成功了!
您得到一个异常,因为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; }
}
}
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();
}
这一切都很顺利。所以除非这个代码:
userManager = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(new ApplicationDbContext()));
ApplicationUser user = userManager.FindByName(HttpContext.Current.User.Identity.Name);
没有返回有效的(自定义)用户对象,问题出在if()
语句上。
你的更新看起来很好,如果你愿意将数据作为声明存储在cookie中,你可以使用它,尽管我个人讨厌那里的try {}
捕获块。
相反,我做的是:
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; }
}
我用一个自定义属性来装饰基本控制器类。
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);
}
}
}
和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));
}
然后我访问WorkContext,如下所示:
public class DashboardController : BaseController
{
public ActionResult Index()
{
ViewBag.User = WorkContext.CurrentUser;
return View();
}
}
我正在使用Ninject的依赖解析程序来解析authService
和cacheManager
,但我相信您可以跳过缓存,用ASP.NET IdentityUserManager
替换authService。
我还想表扬一下WorkContext类在很大程度上受到了NugetGallery项目的启发。
我打赌HttpContext.Current.User为null。因此,取而代之的是:
if (HttpContext.Current.User.Identity.IsAuthenticated)
你可以试试这个:
if (HttpContext.Current.Request.IsAuthenticated)
我也遇到过同样的错误。
我的问题是,对于匿名用户,我没有在IPrincipal上设置IIidentity。我只在用户使用用户名登录时才这样做。否则,IIdentity为null。
我的解决方案是始终设置身份。如果用户未通过身份验证(匿名用户),则IIdentity.IsAuthenticated设置为false。否则,是真的。
我的代码:
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 }
};
}