ASP.NET MVC - Set custom IIdentity or IPrincipal
我需要做一些相当简单的事情:在我的ASP.NET MVC应用程序中,我想设置一个自定义的iIdentity/iprincipal。更容易/更合适。我想扩展默认值,这样我可以调用类似于
我读过很多文章和问题,但我觉得我比实际情况更难理解。我觉得这很容易。如果用户登录,我想设置一个自定义的iIdentity。所以我想,我会在global.asax中实现
所以我想,每当用户登录时,我可以在会话中添加一些必要的变量,这些变量将添加到
我已经为此工作了一天了,我觉得我错过了一些东西。这不应该太难,对吧?我也有点困惑的所有(半)相关的东西,这与此。
如果有人能告诉我一个简单、优雅、高效的解决方案,在一个iIdentity上存储一些额外的数据,而不需要所有额外的模糊。那太好了!我知道有类似的问题,但如果我需要的答案在那里,我一定忽略了。
我就是这样做的。
我决定使用iprincipal而不是iIdentity,因为这意味着我不必同时实现iIdentity和iprincipal。
创建接口
1 2 3 4 5 6 | interface ICustomPrincipal : IPrincipal { int Id { get; set; } string FirstName { get; set; } string LastName { get; set; } } |
客户主体
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | public class CustomPrincipal : ICustomPrincipal { public IIdentity Identity { get; private set; } public bool IsInRole(string role) { return false; } public CustomPrincipal(string email) { this.Identity = new GenericIdentity(email); } public int Id { get; set; } public string FirstName { get; set; } public string LastName { get; set; } } |
CustomPrincipalSerializeModel-用于将自定义信息序列化到FormsAuthenticationTicket对象的UserData字段中。
1 2 3 4 5 6 | public class CustomPrincipalSerializeModel { public int Id { get; set; } public string FirstName { get; set; } public string LastName { get; set; } } |
登录方法-使用自定义信息设置cookie
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | if (Membership.ValidateUser(viewModel.Email, viewModel.Password)) { var user = userRepository.Users.Where(u => u.Email == viewModel.Email).First(); CustomPrincipalSerializeModel serializeModel = new CustomPrincipalSerializeModel(); serializeModel.Id = user.Id; serializeModel.FirstName = user.FirstName; serializeModel.LastName = user.LastName; JavaScriptSerializer serializer = new JavaScriptSerializer(); string userData = serializer.Serialize(serializeModel); FormsAuthenticationTicket authTicket = new FormsAuthenticationTicket( 1, viewModel.Email, DateTime.Now, DateTime.Now.AddMinutes(15), false, userData); string encTicket = FormsAuthentication.Encrypt(authTicket); HttpCookie faCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encTicket); Response.Cookies.Add(faCookie); return RedirectToAction("Index","Home"); } |
global.asax.cs-读取cookie并替换httpcontext.user对象,这是通过重写postAuthenticateRequest完成的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | protected void Application_PostAuthenticateRequest(Object sender, EventArgs e) { HttpCookie authCookie = Request.Cookies[FormsAuthentication.FormsCookieName]; if (authCookie != null) { FormsAuthenticationTicket authTicket = FormsAuthentication.Decrypt(authCookie.Value); JavaScriptSerializer serializer = new JavaScriptSerializer(); CustomPrincipalSerializeModel serializeModel = serializer.Deserialize<CustomPrincipalSerializeModel>(authTicket.UserData); CustomPrincipal newUser = new CustomPrincipal(authTicket.Name); newUser.Id = serializeModel.Id; newUser.FirstName = serializeModel.FirstName; newUser.LastName = serializeModel.LastName; HttpContext.Current.User = newUser; } } |
在Razor视图中访问
1 2 3 | @((User as CustomPrincipal).Id) @((User as CustomPrincipal).FirstName) @((User as CustomPrincipal).LastName) |
在代码中:
1 2 3 | (User as CustomPrincipal).Id (User as CustomPrincipal).FirstName (User as CustomPrincipal).LastName |
我认为代码是不言而喻的。如果不是,请告诉我。
此外,为了使访问更加容易,您可以创建一个基本控制器并重写返回的用户对象(httpcontext.user):
1 2 3 4 5 6 7 | public class BaseController : Controller { protected virtual new CustomPrincipal User { get { return HttpContext.User as CustomPrincipal; } } } |
然后,对于每个控制器:
1 2 3 4 | public class AccountController : BaseController { // ... } |
这将允许您访问以下代码中的自定义字段:
1 2 3 | User.Id User.FirstName User.LastName |
但这在视图内部不起作用。为此,您需要创建自定义的WebViewPage实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | public abstract class BaseViewPage : WebViewPage { public virtual new CustomPrincipal User { get { return base.User as CustomPrincipal; } } } public abstract class BaseViewPage<TModel> : WebViewPage<TModel> { public virtual new CustomPrincipal User { get { return base.User as CustomPrincipal; } } } |
使其成为视图/web.config中的默认页面类型:
1 2 3 4 5 6 7 8 | <pages pageBaseType="Your.Namespace.BaseViewPage"> <namespaces> </namespaces> </pages> |
在视图中,您可以这样访问它:
1 2 | @User.FirstName @User.LastName |
我不能直接说ASP.NET MVC,但对于ASP.NET Web窗体,技巧是创建一个
关于这方面的一篇好文章:http://www.on dotnet.com/pub/a/dotnet/2004/02/02/effectiveformsauth.html(broken link)
编辑:
由于上面的链接被破坏,我建议Lukep在上面的答案中给出解决方案:https://stackoverflow.com/a/10524305-我还建议将接受的答案改为该答案。
编辑2:断开链接的替代方法:https://web.archive.org/web/20120422011422/http://ondotnet.com/pub/a/dotnet/2004/02/02/effectiveformsauth.html
下面是一个完成这项工作的例子。bool isvalid是通过查看一些数据存储来设置的(比如用户数据库)。用户ID只是我正在维护的一个ID。您可以将电子邮件地址等附加信息添加到用户数据中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | protected void btnLogin_Click(object sender, EventArgs e) { //Hard Coded for the moment bool isValid=true; if (isValid) { string userData = String.Empty; userData = userData +"UserID=" + userID; FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(1, username, DateTime.Now, DateTime.Now.AddMinutes(30), true, userData); string encTicket = FormsAuthentication.Encrypt(ticket); HttpCookie faCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encTicket); Response.Cookies.Add(faCookie); //And send the user where they were heading string redirectUrl = FormsAuthentication.GetRedirectUrl(username, false); Response.Redirect(redirectUrl); } } |
在golbal asax中添加以下代码以检索您的信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | protected void Application_AuthenticateRequest(Object sender, EventArgs e) { HttpCookie authCookie = Request.Cookies[ FormsAuthentication.FormsCookieName]; if(authCookie != null) { //Extract the forms authentication cookie FormsAuthenticationTicket authTicket = FormsAuthentication.Decrypt(authCookie.Value); // Create an Identity object //CustomIdentity implements System.Web.Security.IIdentity CustomIdentity id = GetUserIdentity(authTicket.Name); //CustomPrincipal implements System.Web.Security.IPrincipal CustomPrincipal newUser = new CustomPrincipal(); Context.User = newUser; } } |
当您稍后要使用这些信息时,可以按如下方式访问您的自定义主体。
1 2 3 | (CustomPrincipal)this.User or (CustomPrincipal)this.Context.User |
这将允许您访问自定义用户信息。
MVC为您提供了挂起在控制器类中的OnAuthorize方法。或者,可以使用自定义操作筛选器来执行授权。MVC使操作非常简单。我在这里发表了一篇关于这个的博客文章。http://www.bradygaster.com/post/custom-authentication-with-mvc-3.0
如果需要将一些方法连接到@user以在视图中使用,这里有一个解决方案。对于任何正式的成员定制都没有解决方案,但是如果仅视图需要原始问题,那么这可能就足够了。下面用于检查从authorizefilter返回的变量,用于验证此处是否存在某些链接(不用于任何类型的授权逻辑或访问授权)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Security.Principal; namespace SomeSite.Web.Helpers { public static class UserHelpers { public static bool IsEditor(this IPrincipal user) { return null; //Do some stuff } } } |
然后只需在areas web.config中添加一个引用,并在视图中如下所示调用它。
1 | @User.IsEditor() |
好吧,我是一个严肃的密码管理员,我把这个非常古老的问题拖了上来,但有一个更简单的方法,上面的@baserz提到了这个方法。这就是使用C扩展方法和缓存(不要使用会话)的组合。
实际上,微软已经在
因此,您只需要包含名称空间,然后调用
我不确定这是否是缓存的,因为旧的ASP.NET标识不是开放源代码的,而且我没有费心对其进行反向工程。但是,如果不是这样,那么您可以编写自己的扩展方法,这将在特定的时间段内缓存此结果。
根据Lukep的回答,增加了与
- msdn,解释:ASP.NET 2.0中的窗体身份验证
- msdn,formsAuthentication类
- 代码中的.NET访问表单身份验证"超时"值
修改后的lukep代码
1、根据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | int version = 1; DateTime now = DateTime.Now; // respect to the `timeout` in Web.config. TimeSpan timeout = FormsAuthentication.Timeout; DateTime expire = now.Add(timeout); bool isPersist = false; FormsAuthenticationTicket ticket = new FormsAuthenticationTicket( version, name, now, expire, isPersist, userData); |
2,根据
1 2 3 4 | HttpCookie faCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encTicket); // respect to `RequreSSL` in `Web.Config` bool bSSL = FormsAuthentication.RequireSSL; faCookie.Secure = bSSL; |
作为对Web表单用户(不是MVC)lukep代码的补充,如果您想简化页面后面代码的访问,只需将下面的代码添加到一个基页,并在所有页面中派生出基页:
1 2 3 4 5 | Public Overridable Shadows ReadOnly Property User() As CustomPrincipal Get Return DirectCast(MyBase.User, CustomPrincipal) End Get End Property |
因此,在您的代码背后,您可以简单地访问:
1 | User.FirstName or User.LastName |
在Web表单场景中,我缺少的是如何在未绑定到页面的代码中获得相同的行为,例如在httpmodules中,我应该始终在每个类中添加一个强制转换,还是有一种更聪明的方法来获得这种行为?
感谢您的回答,感谢Lukep,因为我使用了您的示例作为我的自定义用户的基础(现在有
我尝试了lukep建议的解决方案,发现它不支持authorize属性。所以我修改了一下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 | public class UserExBusinessInfo { public int BusinessID { get; set; } public string Name { get; set; } } public class UserExInfo { public IEnumerable<UserExBusinessInfo> BusinessInfo { get; set; } public int? CurrentBusinessID { get; set; } } public class PrincipalEx : ClaimsPrincipal { private readonly UserExInfo userExInfo; public UserExInfo UserExInfo => userExInfo; public PrincipalEx(IPrincipal baseModel, UserExInfo userExInfo) : base(baseModel) { this.userExInfo = userExInfo; } } public class PrincipalExSerializeModel { public UserExInfo UserExInfo { get; set; } } public static class IPrincipalHelpers { public static UserExInfo ExInfo(this IPrincipal @this) => (@this as PrincipalEx)?.UserExInfo; } [HttpPost] [AllowAnonymous] [ValidateAntiForgeryToken] public async Task<ActionResult> Login(LoginModel details, string returnUrl) { if (ModelState.IsValid) { AppUser user = await UserManager.FindAsync(details.Name, details.Password); if (user == null) { ModelState.AddModelError("","Invalid name or password."); } else { ClaimsIdentity ident = await UserManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie); AuthManager.SignOut(); AuthManager.SignIn(new AuthenticationProperties { IsPersistent = false }, ident); user.LastLoginDate = DateTime.UtcNow; await UserManager.UpdateAsync(user); PrincipalExSerializeModel serializeModel = new PrincipalExSerializeModel(); serializeModel.UserExInfo = new UserExInfo() { BusinessInfo = await db.Businesses .Where(b => user.Id.Equals(b.AspNetUserID)) .Select(b => new UserExBusinessInfo { BusinessID = b.BusinessID, Name = b.Name }) .ToListAsync() }; JavaScriptSerializer serializer = new JavaScriptSerializer(); string userData = serializer.Serialize(serializeModel); FormsAuthenticationTicket authTicket = new FormsAuthenticationTicket( 1, details.Name, DateTime.Now, DateTime.Now.AddMinutes(15), false, userData); string encTicket = FormsAuthentication.Encrypt(authTicket); HttpCookie faCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encTicket); Response.Cookies.Add(faCookie); return RedirectToLocal(returnUrl); } } return View(details); } |
最后在global.asax.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 | protected void Application_PostAuthenticateRequest(Object sender, EventArgs e) { HttpCookie authCookie = Request.Cookies[FormsAuthentication.FormsCookieName]; if (authCookie != null) { FormsAuthenticationTicket authTicket = FormsAuthentication.Decrypt(authCookie.Value); JavaScriptSerializer serializer = new JavaScriptSerializer(); PrincipalExSerializeModel serializeModel = serializer.Deserialize<PrincipalExSerializeModel>(authTicket.UserData); PrincipalEx newUser = new PrincipalEx(HttpContext.Current.User, serializeModel.UserExInfo); HttpContext.Current.User = newUser; } } |
现在我只需调用
1 | User.ExInfo() |
要注销,我只需打电话
1 | AuthManager.SignOut(); |
AuthManager在哪里
1 | HttpContext.GetOwinContext().Authentication |