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

139

我正在使用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();

然而,User.Identity中没有可用的OrganizationId属性。我需要扩展User.Identity以包含OrganizationId属性吗?如果是这样,我该如何操作?

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


3
我的回答这里对你有帮助吗? - jamesSampica
1
基本上这里我的回答是一样的:http://stackoverflow.com/a/28138594/809357 - 如果你需要在请求的生命周期中经常使用这些信息,你可以将它作为声明放在cookie中。 - trailmax
1
谢谢@Shoe,你们两个的答案都有效。除了你们的答案之外,我还需要添加一个要存储在cookie中的声明。在IdentityModels类中,我必须添加**userIdentity.AddClaim(new Claim("MyApp:OrganizationId", OrganizationId.ToString()));public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)**方法中。 - RobHurd
6个回答

233

每当你想要扩展用户身份(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类中创建身份时,只需像这样添加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 App.Extensions; 在控制器中和 @using. App.Extensions 在 .cshtml 视图文件中。

编辑:

为了避免在每个视图中添加使用语句,您还可以前往Views文件夹,并在其中找到Web.config文件。 现在查找<namespaces>标记,并在其中添加您的扩展名称空间,如下所示:

<add namespace="App.Extensions" />

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

你可以访问扩展方法:

var orgId = User.Identity.GetOrganizationId();

1
@RachitGupta 什么时候发生的?当您尝试添加声明时还是稍后在代码中访问其值时?如果是在添加声明时,请确保您的ApplicationUser已定义该属性... 如果是在代码后面,请不要忘记添加使用语句到您创建扩展方法的位置,例如:using App.Extensions; - Pawel
属性的值来自数据库。您检查过这里是否已经被赋值了吗?使用rIdentity.AddClaim (new Claim("OrganizationId", this.OrganizationId)); 用户.OrganizationId 的值是什么?如果 Claim 为空,则意味着从未分配。 - Pawel
7
谢谢,它有效了。我认为这是在Asp Net Identity 2中使用自定义变量的最佳实践。我不知道为什么Asp.Net社区没有在他们网站的默认文章中提供这样的示例。 - oneNiceFriend
我认为你只需要在你的应用程序中重新登录。 - Petr
@transformer 在数据库优先的情况下添加声明应该是相同的。如果您能让我们看到您的代码,那么就更容易发现可能存在的问题了。在我看来,如果这些属性属于用户,向AspnetUsers表添加属性并没有本质上的错误。您也不仅限于添加作为AspNetUsers表属性的声明。我有一个额外的声明表,但也会根据一些.config值动态创建一些声明。我不会争辩这是否是正确的方法,但有时确实需要这样做。您能发布一篇带有代码的问题吗? - Pawel
显示剩余14条评论

17

我正在寻找相同的解决方案,Pawel给了我99%的答案。唯一缺失的是需要在cshtml(视图)页面中添加以下Razor代码才能将扩展显示出来:

@using programname.Models.Extensions

我想要在用户登录后,在我的导航栏右上方显示名字。

我认为我应该发布这个帖子,以防对其他人有所帮助,所以这是我的代码:

我创建了一个名为Extensions(在我的Models文件夹下)的新文件夹,并创建了一个新类,如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 :

IdentityModels.cs 是一个与 ASP.NET Identity 相关的文件,它定义了应用程序中的用户,角色和声明等标识模型。

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("你好 " + User.Identity.GetUserFirstname() + "!", "Index", "Manage", routeValues: null, htmlAttributes: new { title = "管理" })

也许这会帮助其他人解决类似的问题。


11

看看John Atten写的这篇很棒的博客文章: ASP.NET Identity 2.0: 自定义用户和角色

它提供了整个过程的逐步说明,去读一下吧 : )

以下是一些基础知识。

通过添加新属性(例如-地址、城市、州等),扩展默认的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;

21
这是一个很好的例子,但它并没有回答问题,即如何从User.Identity获取这些新属性。 - Dejan Bogatinovski
6
因为答案没有展示如何从User.Identity中检索自定义属性,所以被投票降权。 - maulik13

6

如果有人在寻找如何在ASP.NET Core 2.1中访问自定义属性,这个问题会很容易解决:您将拥有一个UserManager,例如在_LoginPartial.cshtml中,然后您可以简单地执行以下操作(假设“ScreenName”是您添加到继承自IdentityUser的AppUser中的属性):

@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>
}

2
应该注意到,“GetUserAsync(User)”将查询数据库以检索OrganizationId。相比之下,被接受的解决方案将在声明中包含OrganizationId(例如cookie)。从数据库中获取此信息的好处是可以将人员移动到其他组织而无需要求他们注销/登录。当然,缺点是需要进行额外的数据库查询。 - Matt
我不知道这个!每天我们都会学到新东西。谢谢Jashan! - Emanuel Gianico

3

我还向我的AspNetUsers表中添加或扩展了其他列。当我想要查看这些数据时,我发现许多示例都像上面的代码一样带有“Extensions”等等...这真的让我惊讶,因为你必须编写那么多行代码才能从当前用户中获取一些值。

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

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

1
Dhaust提供了一种很好的方法,可以将属性添加到ApplicationUser类中。从OP代码中可以看出他们可能已经这样做了或者正在这样做。问题是如何从控制器内检索当前登录用户的OrganizationId属性?然而,在User.Identity中并没有提供OrganizationId属性。我需要扩展User.Identity以包括OrganizationId属性吗?
Pawel提供了一种添加扩展方法的方式,需要使用语句或将命名空间添加到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;

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接