[英]ASP.NET MVC - Set custom IIdentity or IPrincipal
我需要做一些相當簡單的事情:在我的ASP.NET MVC應用程序中,我想設置一個自定義IIdentity / IPrincipal。 哪個更容易/更合適。 我想擴展默認值,以便我可以調用User.Identity.Id
和User.Identity.Role
類的東西。 沒什么特別的,只是一些額外的屬性。
我已經閱讀了大量的文章和問題,但我覺得我做得比實際更難。 我覺得這很容易。 如果用戶登錄,我想設置自定義IIdentity。 所以我想,我將在我的global.asax中實現Application_PostAuthenticateRequest
。 但是,每次請求都會調用它,並且我不希望在每個請求上調用數據庫,這些請求將從數據庫請求所有數據並放入自定義IPrincipal對象。 這似乎也是非常不必要,緩慢,並且在錯誤的地方(在那里進行數據庫調用)但我可能是錯的。 或者數據來自何處?
所以我想,每當用戶登錄時,我都可以在我的會話中添加一些必要的變量,我將其添加到Application_PostAuthenticateRequest
事件處理程序中的自定義IIdentity中。 但是,我的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 - 用於將自定義信息序列化到FormsAuthenticationTicket對象中的userdata字段。
public class CustomPrincipalSerializeModel { public int Id { get; set; } public string FirstName { get; set; } public string LastName { get; set; } }
LogIn方法 - 使用自定義信息設置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
(鏈接斷開)
編輯:
由於上面的鏈接被破壞,我會在上面的答案中推薦LukeP的解決方案: https ://stackoverflow.com/a/10524305 - 我還建議將接受的答案改為那個。
編輯2:斷開鏈接的替代方案: https : //web.archive.org/web/20120422011422/http : //ondotnet.com/pub/a/dotnet/2004/02/02/effectiveformsauth.html
這是完成工作的一個例子。 通過查看一些數據存儲(假設您的用戶數據庫)來設置bool isValid。 UserID只是我維護的ID。 您可以向用戶數據添加電子郵件地址等附加信息。
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
合作。
1,根據Web.Config
設置timeout
。 FormsAuthentication.Timeout將獲取超時值,該值在web.config中定義。 我將以下內容包裝成一個功能,返回一張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,根據RequireSSL
配置將cookie配置為安全或不安全。
HttpCookie faCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encTicket);
// respect to `RequreSSL` in `Web.Config`
bool bSSL = FormsAuthentication.RequireSSL;
faCookie.Secure = bSSL;
好吧,所以我在這里拖出這個非常古老的問題是一個嚴肅的密碼管理員,但有一個更簡單的方法,上面的@Baserz觸及了它。 那就是使用C#Extension方法和緩存的組合(不要使用會話)。
實際上,Microsoft已經在Microsoft.AspNet.Identity.IdentityExtensions
命名空間中提供了許多此類擴展。 例如, GetUserId()
是一個返回用戶Id的擴展方法。 還有GetUserName()
和FindFirstValue()
,它根據IPrincipal返回聲明。
因此,您只需要包含命名空間,然后調用User.Identity.GetUserName()
以獲取ASP.NET Identity配置的用戶名。
我不確定這是否被緩存,因為較舊的ASP.NET身份不是開源的,我沒有費心去逆向工程。 但是,如果不是,那么您可以編寫自己的擴展方法,這會將此結果緩存一段特定的時間。
如果您希望簡化頁面后面代碼中的訪問,那么作為Web窗體用戶(而不是MVC)的LukeP代碼的補充,只需將下面的代碼添加到基頁並在所有頁面中派生基頁:
Public Overridable Shadows ReadOnly Property User() As CustomPrincipal
Get
Return DirectCast(MyBase.User, CustomPrincipal)
End Get
End Property
所以在你的代碼背后你可以簡單地訪問:
User.FirstName or User.LastName
我在Web窗體場景中缺少的是如何在與頁面無關的代碼中獲得相同的行為,例如在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);
}
最后在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;
}
}
現在我只需通過調用即可訪問視圖和控制器中的數據
User.ExInfo()
要退出我只是打電話
AuthManager.SignOut();
AuthManager的位置
HttpContext.GetOwinContext().Authentication
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.