如何获取 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 身份以从数据库中获取对声明的更改?的主要内容,如果未能解决你的问题,请参考以下文章
如何在 asp.net 中使用 Windows 身份验证获取用户名?
如何在 Razor 页面中获取 ASP.NET 身份验证票证到期?
如何使用 Microsoft 身份平台身份验证在 ASP.NET Core Web 应用程序中获取 JWT 令牌?