ASP.NET MVC - установка пользовательских значений IIdentity или IPrincipal
Мне нужно сделать что-то довольно простое: в моем приложении ASP.NET MVC я хочу установить пользовательский параметр IIdentity/IPrincipal. Какое бы ни было удобнее и удобнее. Я хочу расширить значение по умолчанию, чтобы я мог называть что-то вроде User.Identity.Id
и User.Identity.Role
. Ничего необычного, просто некоторые дополнительные свойства.
Я прочитал множество статей и вопросов, но мне кажется, что я делаю это сложнее, чем на самом деле. Я думал, что это будет легко. Если пользователь входит в систему, я хочу установить пользовательский параметр IIdentity. Поэтому я подумал, что буду реализовывать Application_PostAuthenticateRequest
в своем global.asax. Тем не менее, это вызвано каждым запросом, и я не хочу делать вызов в базу данных по каждому запросу, который запрашивал бы все данные из базы данных и помещал бы пользовательский объект IPrincipal. Это также кажется очень ненужным, медленным и не в том месте (там используются вызовы базы данных), но я могу ошибаться. Или откуда еще взялись эти данные?
Поэтому я подумал, что всякий раз, когда пользователь входит в систему, я могу добавить некоторые необходимые переменные в свой сеанс, которые я добавляю к пользовательскому идентификатору Identity в обработчике событий Application_PostAuthenticateRequest
. Тем не менее, мой Context.Session
здесь null
, так что это тоже не путь.
Я работаю над этим в течение дня, и я чувствую, что что-то не хватает. Это не должно быть слишком сложно сделать, правильно? Я также немного смущен всем (полу) связанным с этим материалом. MembershipProvider
, MembershipUser
, RoleProvider
, ProfileProvider
, IPrincipal
, IIdentity
, FormsAuthentication
.... Я единственный, кто считает все это очень запутанным?
Если бы кто-то мог сказать мне простое, элегантное и эффективное решение для хранения некоторых дополнительных данных на IIdentity без лишних пухов, это было бы здорово! Я знаю, что есть похожие вопросы по SO, но если мне нужен ответ, я, должно быть, не заметил.
Вот как я это делаю.
Я решил использовать IPrincipal вместо IIdentity, потому что это означает, что мне не нужно реализовывать как IIdentity, так и IPrincipal.
-
Создайте интерфейс
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 - для сериализации пользовательской информации в поле userdata в объекте FormsAuthenticationTicket.
public class CustomPrincipalSerializeModel { public int Id { get; set; } public string FirstName { get; set; } public string LastName { get; set; } }
-
Метод входа в систему - настройка 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 и замена объекта HttpContext.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; } }
-
Доступ в представлениях Razor
@((User as CustomPrincipal).Id) @((User as CustomPrincipal).FirstName) @((User as CustomPrincipal).LastName)
и в коде:
(User as CustomPrincipal).Id
(User as CustomPrincipal).FirstName
(User as CustomPrincipal).LastName
Я думаю, что код не требует пояснений. Если это не так, дайте мне знать.
Кроме того, чтобы сделать доступ еще проще, вы можете создать базовый контроллер и переопределить возвращаемый объект 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
Но это не будет работать внутри представлений. Для этого вам потребуется создать пользовательскую реализацию 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 Web Forms трюк заключается в создании FormsAuthenticationTicket
и шифровании его в файл cookie после аутентификации пользователя. Таким образом, вам нужно только один раз вызвать базу данных (или AD или все, что вы используете для выполнения вашей проверки подлинности), и каждый последующий запрос будет аутентифицироваться на основе билета, хранящегося в файле cookie.
Хорошая статья об этом: http://www.ondotnet.com/pub/a/dotnet/2004/02/02/effectiveformsauth.html (неработающая ссылка)
Edit:
Так как ссылка выше нарушена, я бы порекомендовал решение LukeP в своем ответе выше: fooobar.com/questions/13508/... - Я бы также предположил, что принятый ответ будет изменен на этот один.
Изменить 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
}
}
}
Затем просто добавьте ссылку в области web.config и вызовите ее, как показано ниже в представлении.
@User.IsEditor()
На основе ответа LukeP и добавьте некоторые методы настройки timeout
и requireSSL
, сотрудничающие с Web.config
.
Ссылки ссылок
- MSDN, Explained: Аутентификация форм в ASP.NET 2.0
- MSDN, класс FormsAuthentication
- SO,.net Доступ к форме проверки подлинности "тайм-аут" в коде
Измененные коды LukeP
1, установите timeout
на основе Web.config
. FormsAuthentication.Timeout получит значение тайм-аута, которое определено в web.config. Я завернул следующие функции, которые возвращают a 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;
В качестве дополнения к 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
и многие другие приятные вещи)
Хорошо, поэтому я серьезный криптозащитник, перетащив этот очень старый вопрос, но есть гораздо более простой подход к этому, о котором говорил @Baserz выше. И это должно использовать комбинацию методов расширения С# и кеширования (НЕ использовать сеанс).
Фактически Microsoft уже предоставила ряд таких расширений в пространстве имен Microsoft.AspNet.Identity.IdentityExtensions
. Например, GetUserId()
- это метод расширения, который возвращает идентификатор пользователя. Существует также GetUserName()
и FindFirstValue()
, который возвращает утверждения на основе IPrincipal.
Итак, вам нужно включить только пространство имен, а затем вызвать User.Identity.GetUserName()
, чтобы получить имя пользователя, настроенное с помощью идентификатора ASP.NET.
Я не уверен, что это кэшировано, поскольку более старая идентификация ASP.NET не открыта, и я не потрудился ее перестроить. Однако, если это не так, вы можете написать свой собственный метод расширения, который будет кэшировать этот результат за определенный промежуток времени.
Посмотрите другие вопросы по меткам asp.net asp.net-mvc forms-authentication iprincipal iidentity или Задайте вопрос