ASP.NET MVC - Set custom IIdentity or IPrincipal

后端 未结 9 1415
忘了有多久
忘了有多久 2020-11-21 23:06

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

相关标签:
9条回答
  • 2020-11-21 23:37

    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

    0 讨论(0)
  • 2020-11-21 23:40

    I tried the solution suggested by LukeP and found that it doesn't support the Authorize attribute. So, I modified it a bit.

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

    And finally in Global.asax.cs

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

    Now I can access the data in views and controllers simply by calling

    User.ExInfo()
    

    To log out I just call

    AuthManager.SignOut();
    

    where AuthManager is

    HttpContext.GetOwinContext().Authentication
    
    0 讨论(0)
  • 2020-11-21 23:41

    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.

    0 讨论(0)
  • 2020-11-21 23:45

    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
    
    0 讨论(0)
  • 2020-11-21 23:46

    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)

    0 讨论(0)
  • 2020-11-21 23:49

    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 above: https://stackoverflow.com/a/10524305 - I would also suggest that the accepted answer be changed to that one.

    Edit 2: An alternative for the broken link: https://web.archive.org/web/20120422011422/http://ondotnet.com/pub/a/dotnet/2004/02/02/effectiveformsauth.html

    0 讨论(0)
提交回复
热议问题