ASP.NET в MVC - установка пользовательских IIdentity и iprincipal точно



мне нужно сделать что-то довольно простое: в моем ASP.NET приложение MVC, я хочу установить пользовательский IIdentity / IPrincipal. В зависимости от того, что легче / более подходит. Я хочу расширить значение по умолчанию, чтобы я мог вызвать что-то вроде User.Identity.Id и User.Identity.Role. Ничего особенного, просто некоторые дополнительные свойства.



Я прочитал тонны статей и вопросов, но я чувствую, что я делаю это сложнее, чем есть на самом деле. Я думал, что это будет легко. Если пользователь входит в систему, я хочу установить пользовательский IIdentity. Так Что Я думал, буду реализовывать Application_PostAuthenticateRequest в мой глобальный.асакс. Однако это вызывается по каждому запросу, и я не хочу делать вызов базы данных по каждому запросу, который запрашивал бы все данные из базы данных и помещал в пользовательский объект IPrincipal. Это также кажется очень ненужным, медленным и в неправильном месте (выполнение вызовов базы данных там), но я могу ошибаться. Или откуда еще могли взяться эти данные?



поэтому я подумал, что всякий раз, когда пользователь входит в систему, я могу добавить некоторые необходимые переменные в моем сеансе, который я добавляю к пользовательскому IIdentity в Application_PostAuthenticateRequest обработчик событий. Однако, мой Context.Session и null там, так что это тоже не путь.



Я работаю над этим уже целый день, и я чувствую, что чего-то не хватает. Это не должно быть слишком трудно сделать, не так ли? Я также немного смущен всеми (полу)связанными с этим вещами, которые поставляются с этим. MembershipProvider,MembershipUser,RoleProvider,ProfileProvider,IPrincipal,IIdentity,FormsAuthentication.... Я единственный, кто находит все это очень толку?



если бы кто-нибудь мог сказать мне простое, элегантное и эффективное решение для хранения некоторых дополнительных данных о IIdentity без дополнительного пуха.. это было бы здорово! Я знаю, что есть похожие вопросы на SO, но если ответ, который мне нужен, там, я, должно быть, пропустил.

696   9  

9 ответов:

вот как я это делаю.

я решил использовать IPrincipal вместо IIdentity, потому что это означает, что мне не нужно реализовывать как IIdentity, так и IPrincipal.

  1. создать интерфейс

    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 - для сериализации пользовательских данных в области пользовательских данных в консоли восстановления команда formsauthenticationticket объект.

    public class CustomPrincipalSerializeModel
    {
        public int Id { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
    }
    
  4. метод входа-настройка файла 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");
    }
    
  5. глобальные.асакс.cs-чтение cookie и замена HttpContext.Объект пользователя, это делается путем переопределения 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. доступ в Razor views

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

и в коде:

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

Я думаю, что код не требует пояснений. Если это не так, дайте мне знать.

кроме того, чтобы сделать доступ еще проще, вы можете создать базовый контроллер и переопределить возвращаемый объект пользователя (HttpContext.Пользователь):

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

но это не будет работать внутри видом. Для этого вам нужно будет создать пользовательскую реализацию 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:

<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 веб-формы, фокус в том, чтобы создать FormsAuthenticationTicket и зашифровать его в cookie, как только пользователь прошел проверку подлинности. Таким образом, вам нужно только один раз вызвать базу данных (или AD или что-то еще, что вы используете для выполнения аутентификации), и каждый последующий запрос будет аутентифицироваться на основе билета, хранящегося в файле cookie.

хорошая статья об этом: http://www.ondotnet.com/pub/a/dotnet/2004/02/02/effectiveformsauth.html (сломанная ссылка)

Edit:

поскольку ссылка выше сломана, я бы рекомендовал решение LukeP в своем ответе выше:https://stackoverflow.com/a/10524305 - я бы также предложил, чтобы принятый ответ был изменен на этот.

Edit 2: В качестве альтернативы сломанной ссылке: https://web.archive.org/web/20120422011422/http://ondotnet.com/pub/a/dotnet/2004/02/02/effectiveformsauth.html

вот пример, чтобы получить работу. bool isValid устанавливается путем просмотра некоторого хранилища данных (скажем, вашей пользовательской базы данных). UserID-это просто идентификатор, который я поддерживаю. Вы можете добавить дополнительную информацию, такую как адрес электронной почты для пользовательских данных.

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 для использования в ваших представлениях. Нет решения для какой-либо серьезной настройки членства, но если исходный вопрос был необходим только для просмотров, то этого, возможно, будет достаточно. Ниже была использована для проверки переменной, возвращенной из authorizefilter, используется для проверки, если некоторые ссылки мы должны быть представлены или нет (не для любого вида логики авторизации или предоставления доступа).

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

затем просто добавьте ссылку в области веб.config, и назовите его, как показано ниже в представлении.

@User.IsEditor()

на основе ответ Лукепа и добавить некоторые методы установки timeout и requireSSL сотрудничает с Web.config.

ссылки, ссылки

модифицированных кодов LukeP

1, Set timeout на основе Web.Config. Элемент FormsAuthentication.Тайм-аут получит значение тайм-аута, которое определено в web.конфиг. Я обернул следующие функции, которые возвращают 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, настроить cookie, чтобы быть безопасным или нет, на основании RequireSSL конфигурации.

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

хорошо, поэтому я серьезный криптограф здесь, перетаскивая этот очень старый вопрос, но есть гораздо более простой подход к этому, который был затронут @Baserz выше. А именно использовать комбинацию методов расширения C# и кэширования (не использовать сеанс).

фактически, Microsoft уже предоставила ряд таких расширений в Microsoft.AspNet.Identity.IdentityExtensions пространство имен. Например, GetUserId() метод расширения, который возвращает идентификатор пользователя. Есть также GetUserName() и FindFirstValue(), который возвращает утверждения на основе IPrincipal.

так что вам нужно только включить пространство имен, а затем вызвать User.Identity.GetUserName() чтобы получить имя пользователя в соответствии с настройками ASP.NET личность.

Я не уверен, что это кэшируется, так как старые ASP.NET Identity не является открытым исходным кодом, и я не потрудился его перепроектировать. Однако, если это не так, вы можете написать свой собственный метод расширения, который будет кэшировать этот результат в течение определенного периода времени.

в дополнение к коду LukeP для пользователей веб-форм (не MVC) если вы хотите упростить доступ к коду позади ваших страниц, просто добавьте код ниже на базовую страницу и получите базовую страницу на всех ваших страницах:

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

так что в ваш код позади вы можете просто получить доступ:

User.FirstName or User.LastName

то, что мне не хватает в сценарии веб-формы, - это как получить такое же поведение в коде, не привязанном к странице, например в httpmodules должен ли я всегда добавлять бросить в каждом классе или есть более умный способ получить это?

Спасибо за ваши ответы и спасибо LukeP, так как я использовал ваши примеры в качестве базы для моего пользовательского пользователя (который теперь имеет User.Roles,User.Tasks,User.HasPath(int),User.Settings.Timeout и много других приятных вещей)

я попробовал решение, предложенное LukeP, и обнаружил, что оно не поддерживает атрибут Authorize. Итак, я немного изменил его.

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

и, наконец, в глобальном.асакс.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;
        }
    }

теперь я могу получить доступ к данным в представлениях и контроллерах, просто позвонив

User.ExInfo()

чтобы выйти из системы я просто звоню

AuthManager.SignOut();

где AuthManager

HttpContext.GetOwinContext().Authentication

Comments

    Ничего не найдено.