ASP.NET MVC - Установка пользовательского IIdentity или IPrincipal
Я столкнулся с довольно простой задачей: в моем приложении ASP.NET MVC я хочу установить пользовательский IIdentity / IPrincipal. Что бы ни было проще и подходило лучше, я хочу расширить стандартный интерфейс, чтобы иметь возможность использовать такие свойства, как User.Identity.Id
и User.Identity.Role
. Ничего сложного, просто дополнительные свойства.
Я прочитал кучу статей и вопросов, но мне кажется, что я усложняю задачу больше, чем она есть на самом деле. Я думал, это будет легко. Когда пользователь входит в систему, я хочу установить пользовательский IIdentity. Я решил, что реализую метод Application_PostAuthenticateRequest
в своем файле global.asax. Однако этот метод вызывается при каждом запросе, и я не хочу выполнять запрос к базе данных при каждом обращении, которое бы получало все данные из базы и помещало их в пользовательский объект IPrincipal. Это кажется излишним, медленным и неуместным (выполнять запросы к базе данных в этом месте), но, возможно, я ошибаюсь. Откуда еще могут поступать эти данные?
Тогда я подумал, что когда пользователь входит в систему, я могу добавить необходимые переменные в свою сессию, которые затем добавлю к пользовательскому IIdentity в обработчике события Application_PostAuthenticateRequest
. Однако в этом обработчике Context.Session
оказывается null
, так что этот путь тоже не подходит.
Я уже работаю над этим в течение дня и чувствую, что упускаю что-то важное. Разве это не должно быть слишком сложно? Меня также сбивает с толку вся эта (полу)связанная информация, касающаяся MembershipProvider
, MembershipUser
, RoleProvider
, ProfileProvider
, IPrincipal
, IIdentity
, FormsAuthentication
и т.д. Я один такой, кто находит это все очень запутанным?
Если кто-то может подсказать мне простое, элегантное и эффективное решение для хранения дополнительных данных в IIdentity без лишней возни, я был бы очень благодарен! Я знаю, что на SO есть похожие вопросы, но если ответ, который мне нужен, уже там, значит, я, вероятно, его пропустил.
5 ответ(ов)
Вот как я это делаю.
Я решил использовать IPrincipal вместо IIdentity, так как это означает, что мне не нужно реализовывать оба интерфейса.
Создайте интерфейс
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; } }
Метод LogIn - настройка куки с пользовательской информацией
if (Membership.ValidateUser(viewModel.Email, viewModel.Password)) { var user = userRepository.Users.First(u => u.Email == viewModel.Email); CustomPrincipalSerializeModel serializeModel = new CustomPrincipalSerializeModel { Id = user.Id, FirstName = user.FirstName, 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 - Чтение куки и замена объекта 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) { Id = serializeModel.Id, FirstName = serializeModel.FirstName, 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 (ссылка не работает)
Правка:
Поскольку ссылка выше не работает, я бы рекомендовал решение, предложенное LukeP в его ответе выше: https://stackoverflow.com/a/10524305 - также предлагаю рассмотреть возможность изменения принятого ответа на этот.
Правка 2: Альтернатива для неработающей ссылки: https://web.archive.org/web/20120422011422/http://ondotnet.com/pub/a/dotnet/2004/02/02/effectiveformsauth.html
Вот пример кода, который поможет выполнить поставленную задачу. Переменная isValid
задается на основе данных из какого-либо хранилища (например, вашей базы данных пользователей). userID
— это просто идентификатор, который я использую. Вы можете добавить дополнительную информацию, такую как адрес электронной почты, к данным пользователя.
protected void btnLogin_Click(object sender, EventArgs e)
{
// Временно зафиксировано
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);
// Перенаправляем пользователя на страницу, куда он собирался
string redirectUrl = FormsAuthentication.GetRedirectUrl(username, false);
Response.Redirect(redirectUrl);
}
}
В файле Global.asax
добавьте следующий код для извлечения информации:
protected void Application_AuthenticateRequest(Object sender, EventArgs e)
{
HttpCookie authCookie = Request.Cookies[FormsAuthentication.FormsCookieName];
if (authCookie != null)
{
// Извлекаем куки аутентификации форм
FormsAuthenticationTicket authTicket = FormsAuthentication.Decrypt(authCookie.Value);
// Создаем объект Identity
// CustomIdentity реализует System.Web.Security.IIdentity
CustomIdentity id = GetUserIdentity(authTicket.Name);
// CustomPrincipal реализует System.Web.Security.IPrincipal
CustomPrincipal newUser = new CustomPrincipal();
Context.User = newUser;
}
}
Когда вам потребуется использовать информацию позже, вы можете получить доступ к вашему пользовательскому принципу следующим образом:
(CustomPrincipal)this.User
или
(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; // Выполните необходимые действия
}
}
}
После этого просто добавьте ссылку в web.config вашего области, и вызовите метод в представлении следующим образом:
@User.IsEditor()
Обнаружено потенциально опасное значение Request.Form, полученное от клиента
Можно ли установить неограниченную длину для maxJsonLength в web.config?
Как создать выпадающий список из enum в ASP.NET MVC?
Экранирование символа @ в Razor View Engine
ASP.NET Веб-сайт или ASP.NET Веб-приложение?