如何扩展 User.Identity 的可用属性

Posted

技术标签:

【中文标题】如何扩展 User.Identity 的可用属性【英文标题】:How to extend available properties of User.Identity 【发布时间】:2015-04-04 19:11:48 【问题描述】:

我正在使用 MVC5 Identity 2.0 让用户登录我的网站,其中身份验证详细信息存储在 SQL 数据库中。 Asp.net Identity 已以标准方式实现,可在许多在线教程中找到。

IdentityModels 中的 ApplicationUser 类已扩展为包含一些自定义属性,例如整数 OrganizationId。这个想法是可以创建许多用户并将其分配给一个公共组织以用于数据库关系。

public class ApplicationUser : IdentityUser
    
        public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
        
            // Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
            var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
            // Add custom user claims here
            return userIdentity;
        

        //Extended Properties
        public DateTime? BirthDate  get; set; 
        public long? OrganizationId  get; set; 

        //Key Mappings
        [ForeignKey("OrganizationId")]
        public virtual Organization Organization  get; set; 
    

如何从控制器中检索当前登录用户的 OrganizationId 属性? 这是否可以在用户登录后通过方法获得,还是每次控制器方法执行时我总是根据 UserId 从数据库中检索 OrganizationId?

在网上阅读我看到我需要使用以下内容来获取登录的 UserId 等。

using Microsoft.AspNet.Identity;
...
User.Identity.GetUserId();

但是,OrganizationId 不是 User.Identity 中可用的属性。我是否需要扩展 User.Identity 以包含 OrganizationId 属性?如果是这样,我该怎么做。

我经常需要 OrganizationId 的原因是许多表查询依赖于 OrganizationId 来检索与登录用户关联的组织相关的数据。

【问题讨论】:

我的回答here对你有帮助吗? 我的回答几乎相同:***.com/a/28138594/809357 - 如果您在请求的生命周期中经常需要此信息,您可以将其作为声明放在 cookie 中。 感谢@Shoe,您的两个回答都有效。除了您的答案之外,我还必须添加一个声明以存储在 cookie 中。在 IdentityModels 类中,我必须将 userIdentity.AddClaim(new Claim("MyApp:OrganizationId", OrganizationId.ToString())); 添加到 public async Task GenerateUserIdentityAsync(UserManager manager) 方法。 【参考方案1】:

每当您想使用上述问题的任何其他属性扩展 User.Identity 的属性时,请先将这些属性添加到 ApplicationUser 类,如下所示:

public class ApplicationUser : IdentityUser

    public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
    
        // Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
        var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
        // Add custom user claims here
        return userIdentity;
    

    // Your Extended Properties
    public long? OrganizationId  get; set; 

然后你需要创建一个像这样的扩展方法(我在一个新的 Extensions 文件夹中创建我的):

namespace App.Extensions

    public static class IdentityExtensions
    
        public static string GetOrganizationId(this IIdentity identity)
        
            var claim = ((ClaimsIdentity)identity).FindFirst("OrganizationId");
            // Test for null to avoid issues during local testing
            return (claim != null) ? claim.Value : string.Empty;
        
    

当您在 ApplicationUser 类中创建 Identity 时,只需添加 Claim -> OrganizationId,如下所示:

    public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
    
        // Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
        var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
        // Add custom user claims here => this.OrganizationId is a value stored in database against the user
        userIdentity.AddClaim(new Claim("OrganizationId", this.OrganizationId.ToString()));

        return userIdentity;
    

添加声明并设置好扩展方法后,要将其作为 User.Identity 上的属性提供,在要访问它的页面/文件上添加 using 语句

在我的例子中:using App.Extensions; 在控制器中,@using. App.Extensions 在 .cshtml 视图文件中。

编辑:

您还可以避免在每个视图中添加 using 语句的方法是转到 Views 文件夹,并在其中找到 Web.config 文件。 现在查找 &lt;namespaces&gt; 标签并在那里添加您的扩展命名空间,如下所示:

<add namespace="App.Extensions" />

保存您的文件,您就完成了。现在每个 View 都会知道你的扩展。

您可以访问扩展方法:

var orgId = User.Identity.GetOrganizationId();

【讨论】:

嗨 pawel,我也试过了,但收到错误,应用程序用户不包含 OrganisationId 的定义 @RachitGupta 什么时候发生?当您尝试添加声明或尝试稍后在代码中访问其值时?如果是在您添加声明时,请确保您的 ApplicationUser 已定义属性...如果在代码中稍后,请不要忘记将 using 语句添加到您创建扩展方法的位置,例如: using App.Extensions ; 在 userIdentity.AddClaim 行中出现错误。我只在 IdentityModels.cs 文件中创建了 IdentityExtensions 类。这会是问题的根源吗? 不,如果是在您添加声明时,它会在 identityExtensions 发挥作用之前发生(那是您读回值的时候)...确保您的 ApplicationUser 具有您要添加的属性作为声明:在这个例子中,它是 public long OrganizationId get;放; 谢谢,它成功了。我认为这是 Asp Net Idendity 2 中自定义变量的最佳实践。我不知道为什么 Asp.Net 社区没有在其网站的默认文章中提供这样的示例。【参考方案2】:

我一直在寻找相同的解决方案,Pawel 给了我 99% 的答案。我需要显示扩展的唯一缺少的是将以下 Razor 代码添加到 cshtml(view) 页面中:

@using programname.Models.Extensions

我正在寻找名字,在用户登录后显示在导航栏的右上角。

我想我会发布这个以防它帮助别人,所以这是我的代码:

我创建了一个名为 Extensions(在我的模型文件夹下)的新文件夹,并创建了上面指定的 Pawel 新类:IdentityExtensions.cs

using System.Security.Claims;
using System.Security.Principal;

namespace ProgramName.Models.Extensions

    public static class IdentityExtensions
    
        public static string GetUserFirstname(this IIdentity identity)
        
            var claim = ((ClaimsIdentity)identity).FindFirst("FirstName");
            // Test for null to avoid issues during local testing
            return (claim != null) ? claim.Value : string.Empty;
        
    

IdentityModels.cs

public class ApplicationUser : IdentityUser


    //Extended Properties
    public string FirstName  get; internal set; 
    public string Surname  get; internal set; 
    public bool isAuthorized  get; set; 
    public bool isActive  get; set; 

    public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
    
        // Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
        var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
        // Add custom user claims here
        userIdentity.AddClaim(new Claim("FirstName", this.FirstName));

        return userIdentity;
    

然后在我的_LoginPartial.cshtml(在Views/Shared文件夹下)我添加了@using.ProgramName.Models.Extensions

然后我将更改添加到以下代码行,该代码将在登录后使用用户名:

@Html.ActionLink("Hello " + User.Identity.GetUserFirstname() + "!", "Index", "Manage", routeValues: null, htmlAttributes: new title = "Manage" )

也许这有助于其他人。

【讨论】:

【参考方案3】:

查看 John Atten 的这篇精彩博文: ASP.NET Identity 2.0: Customizing Users and Roles

它有关于整个过程的详细分步信息。去读吧:)

这里有一些基础知识。

通过添加新属性(即地址、城市、州等)来扩展默认的 ApplicationUser 类:

public class ApplicationUser : IdentityUser

    public async Task<ClaimsIdentity> 
    GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
    
        var userIdentity = await manager.CreateIdentityAsync(this,  DefaultAuthenticationTypes.ApplicationCookie);
        return userIdentity;
    
    public string Address  get; set; 
    public string City  get; set; 
    public string State  get; set; 

    // Use a sensible display name for views:
    [Display(Name = "Postal Code")]
    public string PostalCode  get; set; 

    // Concatenate the address info for display in tables and such:
    public string DisplayAddress
    
        get
        
            string dspAddress = string.IsNullOrWhiteSpace(this.Address) ? "" : this.Address;
            string dspCity = string.IsNullOrWhiteSpace(this.City) ? "" : this.City;
            string dspState = string.IsNullOrWhiteSpace(this.State) ? "" : this.State;
            string dspPostalCode = string.IsNullOrWhiteSpace(this.PostalCode) ? "" : this.PostalCode;

            return string.Format("0 1 2 3", dspAddress, dspCity, dspState, dspPostalCode);
        
    

然后将新属性添加到 RegisterViewModel。

    // Add the new address properties:
    public string Address  get; set; 
    public string City  get; set; 
    public string State  get; set; 

然后更新注册视图以包含新属性。

    <div class="form-group">
        @Html.LabelFor(m => m.Address, new  @class = "col-md-2 control-label" )
        <div class="col-md-10">
            @Html.TextBoxFor(m => m.Address, new  @class = "form-control" )
        </div>
    </div>

然后使用新属性更新 AccountController 上的 Register() 方法。

    // Add the Address properties:
    user.Address = model.Address;
    user.City = model.City;
    user.State = model.State;
    user.PostalCode = model.PostalCode;

【讨论】:

这是一个很好的例子,但它没有回答以下问题:如何从 User.Identity 获取这些新属性。 投了反对票,因为答案没有显示如何从 User.Identity 检索自定义属性。【参考方案4】:

对于发现此问题并寻找如何在 ASP.NET Core 2.1 中访问自定义属性的任何人来说,这要容易得多:您将拥有一个 UserManager,例如在 _LoginPartial.cshtml 中,然后您可以简单地执行(假设“ScreenName”是您添加到您自己的 AppUser 的属性,该属性继承自 IdentityUser):

@using Microsoft.AspNetCore.Identity

@using <namespaceWhereYouHaveYourAppUser>

@inject SignInManager<AppUser> SignInManager
@inject UserManager<AppUser> UserManager

@if (SignInManager.IsSignedIn(User)) 
    <form asp-area="Identity" asp-page="/Account/Logout" asp-route-returnUrl="@Url.Action("Index", "Home", new  area = "" )" 
          method="post" id="logoutForm" 
          class="form-inline my-2 my-lg-0">

        <ul class="nav navbar-nav ml-auto">
            <li class="nav-item">
                <a class="nav-link" asp-area="Identity" asp-page="/Account/Manage/Index" title="Manage">
                    Hello @((await UserManager.GetUserAsync(User)).ScreenName)!
                    <!-- Original code, shows Email-Address: @UserManager.GetUserName(User)! -->
                </a>
            </li>
            <li class="nav-item">
                <button type="submit" class="btn btn-link nav-item navbar-link nav-link">Logout</button>
            </li>
        </ul>

    </form>
 else 
    <ul class="navbar-nav ml-auto">
        <li class="nav-item"><a class="nav-link" asp-area="Identity" asp-page="/Account/Register">Register</a></li>
        <li class="nav-item"><a class="nav-link" asp-area="Identity" asp-page="/Account/Login">Login</a></li>
    </ul>

【讨论】:

需要注意的是GetUserAsync(User)会查询数据库获取OrganizationId。相反,接受的解决方案将在声明中包含 OrganizationId(例如 cookie)。从数据库中提取这些信息的好处是人们可以在组织之间移动,而不需要他们注销/登录。当然,缺点是需要额外的数据库查询。【参考方案5】:

我还在我的 AspNetUsers 表中添加或扩展了其他列。当我想简单地查看这些数据时,我发现了许多示例,例如上面带有“扩展”的代码等......这真的让我感到惊讶,您必须编写所有这些代码行只是为了从当前用户那里获得几个值。

事实证明,您可以像查询任何其他表一样查询 AspNetUsers 表:

 ApplicationDbContext db = new ApplicationDbContext();
 var user = db.Users.Where(x => x.UserName == User.Identity.Name).FirstOrDefault();

【讨论】:

【参考方案6】:

Dhaust 提供了一种将属性添加到 ApplicationUser 类的好方法。查看 OP 代码,他们可能已经这样做了,或者正在这样做。问题问

如何从控制器中检索当前登录用户的 OrganizationId 属性?但是,OrganizationId 不是 User.Identity 中可用的属性。我是否需要扩展 User.Identity 以包含 OrganizationId 属性?

Pawel 提供了一种添加扩展方法的方法,该方法需要 using 语句或将命名空间添加到 web.config 文件。

但是,问题询问您是否“需要”扩展 User.Identity 以包含新属性。有一种替代方法可以在不扩展 User.Identity 的情况下访问该属性。如果您遵循 Dhaust 方法,则可以在控制器中使用以下代码来访问新属性。

ApplicationDbContext db = new ApplicationDbContext();
var manager = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(db));
var currentUser = manager.FindById(User.Identity.GetUserId());
var myNewProperty = currentUser.OrganizationId;

【讨论】:

以上是关于如何扩展 User.Identity 的可用属性的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Identity 3.0 中获取当前的 UserId? User.GetUserId 返回 null

我们可以扩展 HttpContext.User.Identity 以在 asp.net 中存储更多数据吗?

如何获得当前用户,以及如何使用User类的MVC5

HttpContext.Current.User.Identity.Name 为空

如何理解 Gtk+ 属性并使 GtkGrid 扩展到可用区域?

ASP.NET 如何在应用程序请求模块中访问 User.Identity.IsAuthenticated?