ASP.NET MVC – 设置自定义IIdentity或IPrincipal
我需要做一些相当简单的事情:在我的ASP.NET MVC应用程序中,我想设置一个自定义的IIdentity / IPrincipal。 无论哪个更容易/更合适。 我想扩展默认,以便我可以调用像User.Identity.Id
和User.Identity.Role
。 没有什么奇特的,只是一些额外的属性。
我已经阅读了大量的文章和问题,但我觉得我比实际上更难。 我认为这很容易。 如果用户login,我想设置一个自定义的IIdentity。 所以我想,我会在我的global.asax中实现Application_PostAuthenticateRequest
。 但是,每个请求都会调用这个方法,而且我不希望在每个请求中调用数据库,这会请求来自数据库的所有数据并放入一个自定义IPrincipal对象。 这也似乎是非常不必要的,缓慢的,并在错误的地方(在那里做数据库调用),但我可能是错的。 或者数据从哪里来?
所以我想,无论何时用户login,我都可以在会话中添加一些必要的variables,我将这些variables添加到Application_PostAuthenticateRequest
事件处理程序中的自定义IIdentity中。 但是,我的Context.Session
在那里是null
,所以这也不是要走的路。
我一直在为此工作一天,我觉得我失去了一些东西。 这不应该太难,对吧? 所有与此相关的(半)相关的东西我也有点困惑。 MembershipProvider
, MembershipUser
, RoleProvider
, ProfileProvider
, IPrincipal
, IIdentity
, FormsAuthentication
….我是唯一一个发现这一切非常混乱的人吗?
如果有人能告诉我一个简单,优雅和高效的解决scheme,在一个IIdentity上存储一些额外的数据,而没有额外的绒毛..这将是伟大的! 我知道有类似的问题,但如果我需要的答案在那里,我一定忽略了。
这是我怎么做的。
我决定使用IPrincipal而不是IIdentity,因为这意味着我不必同时实现IIdentity和IPrincipal。
-
创build界面
interface ICustomPrincipal : IPrincipal { int Id { get; set; } string FirstName { get; set; } string LastName { get; set; } }
-
CustomPrincipal
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字段中。
public class CustomPrincipalSerializeModel { public int Id { get; set; } public string FirstName { get; set; } public string LastName { get; set; } }
-
login方法 – 用自定义信息设置一个cookie
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并replaceHttpContext.User对象,这是通过覆盖PostAuthenticateRequest
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; } }
-
访问剃刀视图
@((User as CustomPrincipal).Id) @((User as CustomPrincipal).FirstName) @((User as CustomPrincipal).LastName)
并在代码中:
(User as CustomPrincipal).Id (User as CustomPrincipal).FirstName (User as CustomPrincipal).LastName
我认为这个代码是不言自明的。 如果不是,请告诉我。
另外为了使访问更容易,您可以创build一个基本控制器并覆盖返回的User对象(HttpContext.User):
public class BaseController : Controller { protected virtual new CustomPrincipal User { get { return HttpContext.User as CustomPrincipal; } } }
然后,对于每个控制器:
public class AccountController : BaseController { // ... }
这将允许您访问代码中的自定义字段,如下所示:
User.Id User.FirstName User.LastName
但是这不会在视图内工作。 为此,您需要创build一个自定义的WebViewPage实现:
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; } } }
将其设置为Views / web.config中的默认页面types:
<pages pageBaseType="Your.Namespace.BaseViewPage"> <namespaces> <add namespace="System.Web.Mvc" /> <add namespace="System.Web.Mvc.Ajax" /> <add namespace="System.Web.Mvc.Html" /> <add namespace="System.Web.Routing" /> </namespaces> </pages>
在视图中,你可以像这样访问它:
@User.FirstName @User.LastName
我不能直接说出ASP.NET MVC,但是对于ASP.NET Web窗体,诀窍是创build一个FormsAuthenticationTicket
并在用户通过身份validation后将其encryption成cookie。 这样,您只需调用一次数据库(或AD或您用来执行身份validation的任何内容),并且每个后续请求都将根据存储在Cookie中的票据进行身份validation。
关于此的一个好文章: http : //www.ondotnet.com/pub/a/dotnet/2004/02/02/effectiveformsauth.html (断开的链接)
编辑:
由于上面的链接被破坏了,所以我会在上面的回答中推荐LukeP的解决scheme: https ://stackoverflow.com/a/10524305 – 我也build议把接受的答案改成那个。
编辑2:链接断开的替代方法: https : //web.archive.org/web/20120422011422/http : //ondotnet.com/pub/a/dotnet/2004/02/02/effectiveformsauth.html
这是一个完成工作的例子。 bool isValid是通过查看一些数据存储设置的(可以说你的用户数据库)。 UserID只是我维护的一个ID。 您可以添加电子邮件地址等附加信息到用户数据。
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添加下面的代码来检索你的信息
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; } }
当您稍后要使用这些信息时,您可以按如下方式访问您的自定义主体。
(CustomPrincipal)this.User or (CustomPrincipal)this.Context.User
这将允许您访问自定义的用户信息。
MVC为您提供了从您的控制器类挂起的OnAuthorize方法。 或者,您可以使用自定义操作筛选器来执行授权。 MVC使得它很容易做到。 我在这里发布了一篇博文。 http://www.bradygaster.com/post/custom-authentication-with-mvc-3.0
如果您需要将一些方法连接到@User以在您的视图中使用,则这是一个解决scheme。 没有任何解决scheme的任何严重的会员定制,但如果原来的问题需要单独的意见,那么这也许就足够了。 下面是用来检查从授权filter返回的variables,用于validation是否有某个链接可以呈现或不是(不适用于任何种类的授权逻辑或访问授予)。
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 } } }
然后在web.config区域添加一个引用,并在视图中调用它。
@User.IsEditor()
根据LukeP的回答 ,并添加一些方法来设置timeout
并要求requireSSL
与Web.config
配合使用。
参考链接
- MSDN,解释:ASP.NET 2.0中的表单身份validation
- MSDN,FormsAuthentication类
- SO,.net访问表单validation代码中的“超时”值
LukeP的修改代码
1,根据Web.Config
设置timeout
。 FormsAuthentication.Timeout将获得在web.config中定义的超时值。 我将以下内容封装成函数,返回一张ticket
。
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,根据RequireSSL
configuration,将Cookieconfiguration为安全或不安全。
HttpCookie faCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encTicket); // respect to `RequreSSL` in `Web.Config` bool bSSL = FormsAuthentication.RequireSSL; faCookie.Secure = bSSL;
作为Web Forms用户(不是MVC)的LukeP代码的一个补充,如果您想简化页面后面代码的访问,只需将下面的代码添加到基本页面并派生出所有页面的基本页面:
Public Overridable Shadows ReadOnly Property User() As CustomPrincipal Get Return DirectCast(MyBase.User, CustomPrincipal) End Get End Property
所以在你的代码后面你可以简单地访问:
User.FirstName or User.LastName
我在Web窗体场景中缺less的是如何获得与页面无关的代码中的相同行为,例如,在httpmodules中 ,我应该总是在每个类中添加一个强制types,还是有更明智的方法来获取?
感谢您的答案,并感谢卢克,因为我用你的例子作为我的自定义用户的基础(现在有User.Roles
, User.Tasks
, User.HasPath(int)
, User.Settings.Timeout
和许多其他好东西)
好的,所以我在这里拖了一个很老的问题,是一个严肃的encryption人,但是有一个更简单的方法来解决这个问题,上面的@Baserz已经提到了这个问题。 那就是使用C#扩展方法和caching的组合(不要使用会话)。
实际上,微软已经在Microsoft.AspNet.Identity.IdentityExtensions
命名空间中提供了许多这样的扩展。 例如, GetUserId()
是一个返回用户ID的扩展方法。 还有GetUserName()
和FindFirstValue()
,它返回基于IPrincipal的声明。
所以你只需要包含名称空间,然后调用User.Identity.GetUserName()
来获取由ASP.NET Identityconfiguration的用户名。
我不确定这是否caching,因为旧的ASP.NET身份不是开源的,我没有打算对其进行逆向工程。 但是,如果不是那么你可以编写自己的扩展方法,这将caching这个结果一段特定的时间。