如何获取 asp.net 身份以从​​数据库中获取对声明的更改?

Posted

技术标签:

【中文标题】如何获取 asp.net 身份以从​​数据库中获取对声明的更改?【英文标题】:How to get asp.net identity to pick up changes to claims from database? 【发布时间】:2021-01-06 21:45:52 【问题描述】:

我正在使用 asp.net 身份和 Identity Server 4 登录我的网站。身份代码使用 SQL 服务器作为其数据存储。当我向 AspNetUserCliams 表添加自定义声明时,注销并重新登录时,我在 ClaimsIdentity.Claims 列表中看不到任何值(旧的或新的)。它确实填充了该表中实际上不存在的许多其他内容:

如何告诉身份服务器实际从表中提取相关声明?

我试过这段代码,但只有当对象在内存中时,才不会将新的声明持久化到数据库中:

ClaimsIdentity id = new ClaimsIdentity();
id.AddClaim(new Claim("MyNewClaim", "bla"));
context.HttpContext.User.AddIdentity(id);

我已经阅读了许多关于难以捉摸的 UserManager 的帖子。但是,我没有看到任何插入到具有 UserManager 或类似签名的 ServiceProvider 中的内容。

我正在使用 Microsoft.AspNetCore.Identity.EntityFrameworkCore,我希望它至少提供足够的 UserManager 来持久化和检索数据,并允许我根据需要覆盖所有内容。这样的实现是否已经存在,还是我必须重新发明***并创建一个 UserManager?

更新:

经过大量的谩骂和搜索,我能够使用以下代码创建 UserManager 实例:

public UserManager<AspNetUser> UserManager

    get
    
        var store = new UserStore<AspNetUser, AspNetRole, AuthorizationDbContext, Guid, AspNetUserClaim, AspNetUserRole, AspNetUserLogin, AspNetUserToken, AspNetRoleClaim>(Context);
        
        var manager = new UserManager<AspNetUser>(
            store, 
            null, 
            new PasswordHasher<AspNetUser>(), 
            new []new UserValidator<AspNetUser>(),
            new IPasswordValidator<AspNetUser>[],
            new UpperInvariantLookupNormalizer(),
            new IdentityErrorDescriber(), 
            null,
            NullLogger<UserManager<AspNetUser>>.Instance);

        return manager;
    

这允许我使用声明更新数据存储区,但在我登录时它不会被返回。

使用 Microsoft NuGet 包的 .Net Core 3.1 v3.10。

我的 Startup.cs 添加了以下内容:

.AddOpenIdConnect("oidc", options =>

    options.Authority = Configuration.GetSection("Authentication")["Authority"];
    options.ClientId = Configuration.GetSection("Authentication")["ClientId"];
    options.ClientSecret = Configuration.GetSection("Authentication")["Secret"];
    options.ResponseType = "code";
    options.SaveTokens = true;
    options.GetClaimsFromUserInfoEndpoint = true;
    options.ClaimActions.MapAll();
    options.Events.OnUserInformationReceived = async context =>
    
        var mi = services.BuildServiceProvider().GetRequiredService<IModuleInfo>();
        mi.ClearLoggedInUsersCache();

        var userManager = services.BuildServiceProvider().GetRequiredService<UserManager<AspNetUser>>();
        var userName = context.User.RootElement.GetString("name");

        // Get the user object from the database.
        var currentUser = (
            from u in userManager.Users
            where u.NormalizedUserName == userName
            select u
        ).FirstOrDefault();

        // Get the claims defined in the database.
        var userClaims = await userManager.GetClaimsAsync(currentUser);

       // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--=-=-=-=-=-
       // Just need to figure out how to attach them to the user.
       // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=--=-=-=-=-=-
    ;

所以现在我可以使用 UserManager 提取声明,但无法将它们持久保存到用户信息中以跨请求进行维护。仍然缺少一块拼图。但 IdentityServer4 没有返回相关声明似乎仍然很奇怪。

【问题讨论】:

【参考方案1】:
ClaimsIdentity id = new ClaimsIdentity();
id.AddClaim(new Claim("MyNewClaim", "bla"));
context.HttpContext.User.AddIdentity(id);

嗨@Ed Williams,

通过使用上面的代码,新的声明将添加到 HttpContext 中,它将在处理单个请求时存储数据。处理请求后,集合的内容将被丢弃。

要将声明存储在 AspNetUserClaims 表中,我们可以使用UserManager.AddClaimAsync() 方法将指定的声明添加到用户。检查以下示例代码:

使用以下代码创建一个 ClaimsController:

[Authorize]
public class ClaimsController : Controller

    private UserManager<IdentityUser> userManager;
    private SignInManager<IdentityUser> signInManager; //used to signin again and get the latest claims.

    public ClaimsController(UserManager<IdentityUser> userMgr, SignInManager<IdentityUser> signMgr)
    
        userManager = userMgr;
        signInManager = signMgr;
    
    public IActionResult Index()
    
        return View(User?.Claims);
    
    public IActionResult Create()
    
        return View();
    
    [HttpPost]
    public async Task<IActionResult> CreateAsync(string claimType, string claimValue)
    
        IdentityUser user = await userManager.GetUserAsync(HttpContext.User);
        Claim claim = new Claim(claimType, claimValue, ClaimValueTypes.String);
        IdentityResult result = await userManager.AddClaimAsync(user, claim);

        HttpContext.User.Identities.FirstOrDefault().AddClaim(claim);
        
        //signin again and get the latest claims.
        await signInManager.SignInAsync(user, false, null);

        if (result.Succeeded)
            return RedirectToAction("Index");
        else
            Errors(result);
        return View("Index", User.Claims);
    

    [HttpPost]
    public async Task<IActionResult> Delete(string claimValues)
    
        IdentityUser user = await userManager.GetUserAsync(HttpContext.User);

        string[] claimValuesArray = claimValues.Split(";");
        string claimType = claimValuesArray[0], claimValue = claimValuesArray[1], claimIssuer = claimValuesArray[2];
        Claim claim = User.Claims.Where(x => x.Type == claimType && x.Value == claimValue && x.Issuer == claimIssuer).FirstOrDefault();

        IdentityResult result = await userManager.RemoveClaimAsync(user, claim);

        await signInManager.SignInAsync(user, false, null);

        if (result.Succeeded)
            return RedirectToAction("Index");
        else
            Errors(result);

        return View("Index", User.Claims);
     

    void Errors(IdentityResult result)
    
        foreach (IdentityError error in result.Errors)
            ModelState.AddModelError("", error.Description);
    

索引页面中的代码(Index.cshtml):

@model IEnumerable<System.Security.Claims.Claim>
<h2 class="bg-primary m-1 p-1 text-white">Claims</h2>
<a asp-action="Create" class="btn btn-secondary">Create a Claim</a>
<table class="table table-sm table-bordered">
    <tr>
        <th>Subject</th>
        <th>Issuer</th>
        <th>Type</th>
        <th>Value</th>
        <th>Delete</th>
    </tr>

    @foreach (var claim in Model.OrderBy(x => x.Type))
    
        <tr>
            <td>@claim.Subject.Name</td>
            <td>@claim.Issuer</td>
            <td>@claim.Type</td>
            <td>@claim.Value</td>
            <td>
                <form asp-action="Delete" method="post">
                    <input type="hidden" name="claimValues" value="@claim.Type;@claim.Value;@claim.Issuer" />
                    <button type="submit" class="btn btn-sm btn-danger">
                        Delete
                    </button>
                </form>
            </td>
        </tr>
    
</table>

创建页面中的代码(Create.cshtml):

@model System.Security.Claims.Claim
@
    ViewData["Title"] = "Create";


<h1>Create</h1>

<h1 class="bg-info text-white">Create Claim</h1>
<a asp-action="Index" class="btn btn-secondary">Back</a>
<div asp-validation-summary="All" class="text-danger"></div>

<form asp-action="Create" asp-controller="Claims" method="post">
    <div class="form-group">
        <label for="ClaimType">Claim Type:</label>
        <input name="ClaimType" class="form-control" />
    </div>
    <div class="form-group">
        <label for="ClaimValue">Claim Value:</label>
        <input name="ClaimValue" class="form-control" />
    </div>
    <button type="submit" class="btn btn-primary">Create</button>
</form>

[注意] 使用上面的代码,在对用户添加或删除声明后,我们必须刷新当前用户,在这个示例中,我使用SignInManager.SignInAsync()方法重新登录并更新声明.

然后,截图如下:

编辑:

关于UserManager,它来源于Microsoft.AspNetCore.Identity和Microsoft.Extensions.Identity.Core.dll。

在 Asp.Net Core 3.1+ 版本应用程序中,配置身份和数据库后,在 Startup.ConfigureServices 中使用以下代码(这里您可能需要安装 EntityFrameWork 包,在我的示例中我安装了these packages):

    public void ConfigureServices(IServiceCollection services)
    
        services.AddDbContext<ApplicationDbContext>(options =>
            options.UseSqlServer(
                Configuration.GetConnectionString("DefaultConnection"))); 

        services.AddIdentity<IdentityUser, IdentityRole>(options => options.SignIn.RequireConfirmedAccount = true)
             .AddDefaultUI()
             .AddEntityFrameworkStores<ApplicationDbContext>()
             .AddDefaultTokenProviders();  
        services.AddControllersWithViews().AddJsonOptions(opts =>
        
            opts.JsonSerializerOptions.Encoder = System.Text.Encodings.Web.javascriptEncoder.UnsafeRelaxedJsonEscaping;
        );
        services.AddRazorPages(); 
    

ApplicationDbContext 继承自 IdentityDbContext

public class ApplicationDbContext : IdentityDbContext

    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
        : base(options)
    
    

[注意] 您可能需要使用Migration 来生成数据库。

之后,在控制器中,我们可以使用Dependency injection注册UserManager,代码如下:

public class ClaimsController : Controller

    private readonly UserManager<IdentityUser> userManager;
    private readonly SignInManager<IdentityUser> signInManager; //used to signin again and get the latest claims.

    public ClaimsController(UserManager<IdentityUser> userMgr, SignInManager<IdentityUser> signMgr)
    
        userManager = userMgr;
        signInManager = signMgr;
    

更多详细信息,您可以查看以下文章:

Asp.net core Identity

Scaffold Identity in ASP.NET Core projects

最后,如果还是不能使用UserManager,作为一种变通方法,您可以通过dbcontext直接访问AspNetUserClaims表,然后使用EF核心管理用户的Claims。请参考以下代码:

[Authorize]
public class ClaimsController : Controller

    private readonly UserManager<IdentityUser> userManager;
    private readonly SignInManager<IdentityUser> signInManager; //used to signin again and get the latest claims.

    private readonly ApplicationDbContext _dbcontext;
    public ClaimsController(UserManager<IdentityUser> userMgr, SignInManager<IdentityUser> signMgr, ApplicationDbContext context)
    
        userManager = userMgr;
        signInManager = signMgr;
        _dbcontext = context;
    
    public IActionResult Index()
    
        // access the UserClaims table and get the User's claims 
        var claims = _dbcontext.UserClaims.ToList();
        //loop through the claims
        //then based on the resut to creat claims
        //Claim claim = new Claim(claimType, claimValue, ClaimValueTypes.String);

        //and using the following code to add claim to current user.
        HttpContext.User.Identities.FirstOrDefault().AddClaim(claim);
        
        return View(HttpContext.User?.Claims);
    

要删除当前用户的声明,您可以尝试使用以下代码:

  HttpContext.User.Identities.FirstOrDefault().RemoveClaim(claim);

【讨论】:

我现在遇到的最大问题是获取 UserManager 的实例。我曾假设通过将 Asp.Net Identity 与 Entity Framework 一起使用,该代码将公开 UserManager,但我没有在服务提供者中找到它以传递给构造函数。我需要创建自己的 UserManager 吗? 很好的回应,顺便说一句@Zhi-lv。谢谢。 嗨@EdWilliams,您使用的是哪个版本的Asp.net Core 和Identity?在上面的示例中,我使用的是 asp.net 5 和 EntityFrameworkCore v5.0.1 版本,请在我的回复中查看我的编辑,配置身份后我们可以使用用户管理器。另外,我建议你可以查看this thread。 关于创建你自己的UserManager,你可以检查Custom Identity,可能你必须自定义提供程序并覆盖内部方法,这很复杂。如果是这样的话,你可以考虑直接通过EF核心管理AspNetUserClaims表,不使用UserManager,查看我的回复。

以上是关于如何获取 asp.net 身份以从​​数据库中获取对声明的更改?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 asp.net 控制器中获取用户身份?

如何在 asp.net 中使用 Windows 身份验证获取用户名?

如何在 Razor 页面中获取 ASP.NET 身份验证票证到期?

如何使用 Microsoft 身份平台身份验证在 ASP.NET Core Web 应用程序中获取 JWT 令牌?

ASP.NET MVC 5 - 身份。如何获取当前的ApplicationUser

使用 ASP.NET 表单身份验证时如何获取当前登录用户的用户 ID?