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, но если мне нужен ответ, я, должно быть, не заметил.
Ответы
Ответ 1
Вот как я это делаю.
Я решил использовать 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
Ответ 2
Я не могу говорить напрямую для 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
Ответ 3
Вот пример, чтобы выполнить работу. 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
это позволит вам получить доступ к пользовательской пользовательской информации.
Ответ 4
MVC предоставляет вам метод OnAuthorize, зависающий от ваших классов контроллера. Или вы можете использовать настраиваемый фильтр действий для выполнения авторизации. MVC делает это довольно легко. Я разместил сообщение в блоге об этом здесь. http://www.bradygaster.com/post/custom-authentication-with-mvc-3.0
Ответ 5
Вот решение, если вам нужно подключить некоторые методы к @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()
Ответ 6
На основе ответа LukeP и добавьте некоторые методы настройки timeout
и requireSSL
, сотрудничающие с Web.config
.
Ссылки ссылок
Измененные коды 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;
Ответ 7
В качестве дополнения к 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
и многие другие приятные вещи)
Ответ 8
Хорошо, поэтому я серьезный криптозащитник, перетащив этот очень старый вопрос, но есть гораздо более простой подход к этому, о котором говорил @Baserz выше. И это должно использовать комбинацию методов расширения С# и кеширования (НЕ использовать сеанс).
Фактически Microsoft уже предоставила ряд таких расширений в пространстве имен Microsoft.AspNet.Identity.IdentityExtensions
. Например, GetUserId()
- это метод расширения, который возвращает идентификатор пользователя. Существует также GetUserName()
и FindFirstValue()
, который возвращает утверждения на основе IPrincipal.
Итак, вам нужно включить только пространство имен, а затем вызвать User.Identity.GetUserName()
, чтобы получить имя пользователя, настроенное с помощью идентификатора ASP.NET.
Я не уверен, что это кэшировано, поскольку более старая идентификация ASP.NET не открыта, и я не потрудился ее перестроить. Однако, если это не так, вы можете написать свой собственный метод расширения, который будет кэшировать этот результат за определенный промежуток времени.