【问题标题】:How to get asp.net identity to pick up changes to claims from database?如何获取 asp.net 身份以从​​数据库中获取对声明的更改?
【发布时间】: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 没有返回相关声明似乎仍然很奇怪。

【问题讨论】:

    标签: asp.net-core-mvc asp.net-identity


    【解决方案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,查看我的回复。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-06-17
    • 2020-04-22
    • 2019-01-04
    • 2016-04-29
    • 2022-10-21
    • 1970-01-01
    相关资源
    最近更新 更多