将光标移到/点击文章中的句子上,可以查看译文。      显示繁体中文内容    显示简体中文内容

ASP.NET MVC - Set custom IIdentity or IPrincipal
快速修改问答信息 ASP.NET MVC - - 设置自定义IIdentity或者IPrincipal

I need to do something fairly simple :in my ASP.NET MVC application, i want to set a custom IIdentity/IPrincipal.Whichever is easier/more suitable. i want to extend the default so that i can call something like User.Identity.Id and User.Identity.Role.nothing fancy, just some extra properties.

I've read tons of articles and questions but i feel like i'm making it harder than it actually is.i thought it would be easy.if a user logs on, i want to set a custom IIdentity.so i thought, i will implementApplication_PostAuthenticateRequestin my global.asax. However, that is called on every request, and i don't want to do a call to the database on every request which would request all the data from the database and put in a custom IPrincipal object.that also seems very unnecessary, slow, and in the wrong place (doing database calls there) but i could be wrong.or where else would that data come from?

So i thought, whenever a user logs in, i can add some necessary variables in my session, which i add to the custom IIdentity in theApplication_PostAuthenticateRequestevent handler. However, my Context.Session is null there, so that is also not the way to go.

I've been working on this for a day now and i feel i'm missing something.this shouldn't be too hard to do, right?i'm also a bit confused by all the (semi)related stuff that comes with this.MembershipProvider, MembershipUser, RoleProvider, ProfileProvider, IPrincipal, IIdentity, FormsAuthentication.. ..am i the only one who finds all this very confusing?

If someone could tell me a simple, elegant, and efficient solution to store some extra data on a IIdentity without all the extra fuzz..that would be great! i know there are similar questions on so but if the answer i need is in there, i must've overlooked.Thanks.

时间:

Here's how i do it.

I decided to use IPrincipal instead of IIdentity because it means i don't have to implement both IIdentity and IPrincipal.

  1. Create the interface

    
    interface ICustomPrincipal : IPrincipal
    {
     int Id { get; set; }
     string FirstName { get; set; }
     string LastName { get; set; }
    }
    
    
  2. 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; }
    }
    
    
  3. CustomPrincipalSerializeModel - for serializing custom information into userdata field in FormsAuthenticationTicket object.

    
    public class CustomPrincipalSerializeModel
    {
     public int Id { get; set; }
     public string FirstName { get; set; }
     public string LastName { get; set; }
    }
    
    
  4. LogIn method - setting up a cookie with custom information

    
    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");
    }
    
    
  5. Global.asax.cs - Reading cookie and replacing HttpContext.User object, this is done by overriding 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;
     }
    }
    
    
  6. Access in Razor views

    
    @((User as CustomPrincipal).Id)
    @((User as CustomPrincipal).FirstName)
    @((User as CustomPrincipal).LastName)
    
    

and in code :


 (User as CustomPrincipal).Id
 (User as CustomPrincipal).FirstName
 (User as CustomPrincipal).LastName

I think the code is self-explanatory.if it isn't, let me know.

Additionally to make the access even easier you can create a base controller and override the returned User object (HttpContext.User) :


public class BaseController : Controller
{
 protected virtual new CustomPrincipal User
 {
 get { return HttpContext.User as CustomPrincipal; }
 }
}

and then, for each controller :


public class AccountController : BaseController
{
//...
}

which will allow you to access custom fields in code like this :


User.Id
User.FirstName
User.LastName

But this will not work inside views.for that you would need to create a custom WebViewPage implementation :


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; }
 }
}

Make it a default page type in Views/web.config :


<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>

and in views, you can access it like this :


@User.FirstName
@User.LastName

HTH

I can't speak directly for ASP.NET MVC, but for ASP.NET Web Forms, the trick is to create a FormsAuthenticationTicket and encrypt it into a cookie once the user has been authenticated.this way, you only have to call the database once (or AD or whatever you are using to perform your authentication), and each subsequent request will authenticate based on the ticket stored in the cookie.

A good article on this :http://www.ondotnet.com/pub/a/dotnet/2004/02/02/effectiveformsauth. html(broken link )

Edit :

Since the link above is broken, i would recommend LukeP's solution in his answer below :http://stackoverflow.com/a/10524305 - i would also suggest that the accepted answer be changed to that one.

Here is an example to get the job done.bool isValid is set by looking at some data store (lets say your user data base).UserID is just an ID i am maintaining.you can add aditional information like email address to user data.


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);
 }
}

in the golbal asax add the following code to retrive your information


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;
 }
}

When you are going to use the information later, you can access your custom principal as follows.


(CustomPrincipal)this.User
or 
(CustomPrincipal)this.Context.User

this will allow you to access custom user information.

MVC provides you with the OnAuthorize method that hangs from your controller classes.Or, you could use a custom action filter to perform authorization.MVC makes it pretty easy to do.i posted a blog post about this here.http://www.bradygaster.com/post/custom-authentication-with-mvc-3.0

Here is a solution if you need to hook up some methods to @User for use in your views.no solution for any serious membership customization, but if the original question was needed for views alone then this perhaps would be enough.the below was used for checking a variable returned from a authorizefilter, used to verify if some links wehere to be presented or not(not for any kind of authorization logic or access granting).


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
 }
 }
 }

Then just add a reference in the areas web.config, and call it like below in the view.

 
@User.IsEditor()

 

Based on LukeP's answer, and add some methods to setup timeout and requireSSL cooperated with Web.config.

The references links

Modified Codes of LukeP

1, Set timeout based on Web.config.the FormsAuthentication.Timeout will get the timeout value, which is defined in web.config.i wrapped the followings to be a function, which return a ticket back.


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, Configure the cookie to be secure or not, based on the requireSSL configuration.


HttpCookie faCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encTicket);
//respect to `RequreSSL` in `Web.Config`
bool bSSL = FormsAuthentication.RequireSSL;
faCookie.Secure = bSSL;

As an addition to LukeP code for Web Forms users (not MVC) if you want to simplify the access in the code behind of your pages, just add the code below to a base page and derive the base page in all your pages :


Public Overridable Shadows ReadOnly Property User() As CustomPrincipal
 Get
 Return DirectCast(MyBase.User, CustomPrincipal)
 End Get
End Property

So in your code behind you can simply access :


User.FirstName or User.LastName

What i'm missing in a Web Form scenario, is how to obtain the same behaviour in code not tied to the page, for example in httpmodules should i always add a cast in each class or is there a smarter way to obtain this?

Thanks for your answers and thank to LukeP since i used your examples as a base for my custom user (which now has User.Roles, User.Tasks, User.HasPath(int), User.Settings.Timeout and many other nice things )

You can open the asp.net mvc 4 web application template project (in vs2013 at least) and see how it's implemented there.

I'm going to try and use it too.

...