使用 MVC5 和 OWIN 的自定义身份
Posted
技术标签:
【中文标题】使用 MVC5 和 OWIN 的自定义身份【英文标题】:Custom Identity using MVC5 and OWIN 【发布时间】:2014-03-07 22:32:51 【问题描述】:我尝试将自定义属性添加到使用 MVC5 和 OWIN 身份验证的网站的 ApplicationUser。我已经阅读了https://***.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
第二个链接中的 cmets 指出我可能使用了声明 (http://msdn.microsoft.com/en-us/library/ms734687.aspx?cs-save-lang=1&cs-lang=csharp) ,但链接到的文章没有显示如何将它们添加到 IPrincipal
(这就是 HttpContext.Current.User
是什么),或者在管道中,您应该将它们添加到ClaimsIdentity
(这是User
的具体类)。我倾向于使用声明,但我需要知道在哪里向用户添加这些新声明。
即使声明是要走的路,我也很好奇我的自定义 IPrincipal 做错了什么,因为我似乎已经实现了它所需的一切。
【问题讨论】:
把我之前的更新移到了一个答案,好像更合适 这是我写的一个例子,它利用了像AuthenticationFilter
这样的 MVC5 特性:http://***.com/a/25731422/264672
嗨,黑冰。你是如何让这些角色发挥作用的? WindowsBuiltInRole
是什么?我还遵循了 LukeP 的确切代码(在您链接的线程中),但对我来说,当我执行 [Authorize(Roles="Admin")]
时它不起作用。
@Ciwan WindowsBuiltInRole 是来自 Windows 安全的枚举,msdn.microsoft.com/en-us/library/…
没关系,事实证明 LukeP 的答案是“旧的”。在 MVC5 上,我们假设使用 Claims。这是真的!效果很好。
【参考方案1】:
我可以使用基于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-save-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;
这使得整个事情正常工作!
【讨论】:
Claims 似乎比其他选项更少干扰,所以我选择了这个解决方案。 我也没有设置导致上述错误的 IIdentity。所以我建议仔细检查 IParticipal 实现的属性以避免错误。【参考方案2】:您遇到了异常,因为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
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;
我用自定义属性装饰了基本控制器类。
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 的 Dependency Resolver 来解析 authService
和 cacheManager
,但我相信您可以跳过缓存并将 authService 替换为 ASP.NET Identity UserManager
。
我还想在应得的地方给予赞扬,因为 WorkContext 类深受 NugetGallery 项目的启发。
【讨论】:
这不是我的错误的原因,我只有在User
被替换时才会出现异常,只有当IsAuthenticated
为真时才会发生这种情况。
@BlackICE 在我的测试场景中,似乎 Application_PostAuthenticateRequest 在每个请求上都会触发两次,如果我在 if 子句上设置断点,它第一次运行 HttpContext.Current.User (或 Identity)是null 并且我在子句本身上得到一个 null 异常。您能否在该 if
子句上放置一个断点并查看其触发时 Identity 是否为空?如果是,IsAuthenticated 将抛出 NullReferenceException。
嗨卢克 - 希望你看到这是一个旧帖子。无论如何,我无法让 WorkContext.cs 工作。查看gist.github.com/ajtatum/5d06a2dbcc6d1172efae609720b6a095。我确实很喜欢您的解决方案:***.com/a/10524305,因为访问这些属性要容易得多。我希望它可以与 OWIN 一起使用,主要是因为它通过 Google 等处理外部登录/注册。我在这里提出了一个问题:***.com/questions/39680253/… 如果您愿意提供您的意见,那就太好了!谢谢! -AJ【参考方案3】:
我打赌 HttpContext.Current.User 为空。所以不要这样:
if (HttpContext.Current.User.Identity.IsAuthenticated)
你可以试试这个:
if (HttpContext.Current.Request.IsAuthenticated)
【讨论】:
不,它通过我的方法成功实现了,它在 MVC 代码的某处出错。【参考方案4】:我遇到了同样的错误。
我的问题是,对于匿名用户,我没有在 IPrincipal 上设置 IIdentity。我只在用户使用用户名登录时才这样做。否则,IIdentity 为空。
我的解决方案是始终设置 IIdentity。如果用户未通过身份验证(匿名用户),则 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
;
【讨论】:
以上是关于使用 MVC5 和 OWIN 的自定义身份的主要内容,如果未能解决你的问题,请参考以下文章
带有 Microsoft.Owin.Security.OpenIdConnect 和 AzureAD v 2.0 端点的自定义参数
Owin 和 Windows 身份验证 (mvc5) - 使用 Windows 身份验证作为登录的一部分
在 MVC5 中使用 OWIN Oauth 的 Google 身份验证未命中 ExternalLoginCallback 函数