如何在登录时向 HttpContext 用户添加声明
How to add claims to the HttpContext User on sign in
此 post 可能很长,但包含答案所需的所有相关详细信息。
我一直在搜索,并发现许多其他人也在寻找向 HttpContext 用户添加声明的正确方法,以便在需要时可以使用 Razor 在视图中检索这些声明。
例如,
在默认的 Asp.Net Core 2.0 Web 应用程序中,_LoginPartial 具有显示用户电子邮件的代码。如果我想将其更改为用户全名(这假设注册过程包括名字和姓氏条目,并对 ApplicationUser class)
// Add profile data for application users by adding properties to the ApplicationUser class
public class ApplicationUser : IdentityUser
{
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime DateOfBirth { get; set; }
public Gender Gender { get; set; }
...balance of code removed for brevity
}
我想为用户添加他们的全名和性别的声明,而不是默认应用程序中当前使用的 UserManager 方法。 (还有其他人)
当前默认的 wep 应用代码
@if (SignInManager.IsSignedIn(User))
{
<form asp-area="" asp-controller="Account" asp-action="Logout" method="post" id="logoutForm" class="navbar-right">
<ul class="nav navbar-nav navbar-right">
<li>
<a asp-area="" asp-controller="Manage" asp-action="Index" title="Manage">Hello @UserManager.GetUserName(User)!</a>
</li>
<li>
<button type="submit" class="btn btn-link navbar-btn navbar-link">Log out</button>
</li>
</ul>
</form>
}
else
{
...code removed for brevity
}
我希望完成的事情;
替换这个,
<a asp-area="" asp-controller="Manage" asp-action="Index" title="Manage">Hello @UserManager.GetUserName(User)!</a>
有了这个
<a asp-area="" asp-controller="Manage" asp-action="Index" title="Manage">Hello @((ClaimsIdentity) User.Identity).GetSpecificClaim("avatarUrl")!</a>
注意:GetSpecificClaim 是一种检索声明的扩展方法。
我认为添加声明的最佳位置是在登录方法中。
public async Task<IActionResult> Login(LoginViewModel model, string returnUrl = null)
{
ViewData["ReturnUrl"] = returnUrl;
if (!ModelState.IsValid) return View(model);
// Now model is valid, require the user to have a confirmed email before they can log on.
var user = await _userManager.FindByEmailAsync(model.Email);
if (user != null)
{
if (!await _userManager.IsEmailConfirmedAsync(user))
{
ModelState.AddModelError(string.Empty,
"You must have a confirmed email to log in.");
return View(model);
}
}
else
{
ModelState.AddModelError(string.Empty,
"There is no registered account for the email address supplied.");
return View(model);
}
var result = await _signInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, lockoutOnFailure: true);
if (result.Succeeded)
{
_logger.LogInformation("User logged in.");
// Add claims to signed in user
var userClaims = HttpContext.User.Claims.ToList();
userClaims.Add(new Claim("fullname", user.GetFullName(user.UserName)));
userClaims.Add(new Claim("avatarUrl", user.AvatarUrl));
// Using ClaimsTransformer
// Add claims here for the logged in user using AddUserInfoClaimsAsync extension method
**var ct = new ClaimsHelpers.ClaimsTransformer();
var identityWithInfoClaims = await ct.AddUserInfoClaimsAsync(User, user);**
return RedirectToLocal(returnUrl);
}
if (result.RequiresTwoFactor)
{
return RedirectToAction(nameof(LoginWith2Fa), new { returnUrl, model.RememberMe });
}
if (result.IsLockedOut)
{
_logger.LogWarning("User account locked out.");
return RedirectToAction(nameof(Lockout));
}
ModelState.AddModelError(string.Empty, "Invalid login attempt.");
return View(model);
}
但 userClaims 变量始终为空
问题:
- 为什么刚刚设置的声明列表是空的?
- 是否有不同类型的身份声明?
- 有更好的方法吗?
更新:
我在早期的一些尝试中将 ClaimsTransformer 放在一起,我可以使用它添加声明(请参阅上面登录控制器代码中的粗体更改)但是我现在如何处理 ClaimsPrincipal 变量 identityWithinfoClaims?我不能将 User 设置为等于它,因为 User 是只读的,那么如何正确使用添加了声明的对象?
要添加或转换自定义声明,请实施并使用自定义 ClaimsAuthenticationManager
。 How To: Transform Incoming Claims.
public class ClaimsTransformationModule : ClaimsAuthenticationManager {
public override ClaimsPrincipal Authenticate(string resourceName, ClaimsPrincipal incomingPrincipal) {
if (incomingPrincipal != null && incomingPrincipal.Identity.IsAuthenticated == true) {
var identity = (ClaimsIdentity)incomingPrincipal.Identity;
var user = GetUserData(identity);
identity.AddClaim(new Claim("fullname", user.GetFullName(user.UserName)));
identity.AddClaim(new Claim("avatarUrl", user.AvatarUrl));
}
return incomingPrincipal;
}
}
此处,GetUserData()
根据用户名从数据库中检索用户实体。
在 web.config
中注册此转换器:
<system.identityModel>
<identityConfiguration>
<claimsAuthenticationManager type="MyProject.ClaimsTransformationModule , MyProject, Version=1.0.0.0, Culture=neutral" />
</identityConfiguration>
</system.identityModel>
我们前段时间遇到了完全相同的问题。
解决方案相当简单。您只需要创建自己的 IUserClaimsPrincipalFactory
接口实现并将其注册到 DI 容器中。当然,没有必要从头开始编写该接口的实现 - 您可以从 UserClaimsPrincipalFactory
派生 class 并只覆盖一个方法。
这里是 step-by-step description 包括代码片段。
如果您有 .NET core
中间件管道(或其他自定义设置),您可以在其中处理 authentication
/authorization
并实例化 Claim
,您可以直接添加它像这样 HttpContext
(不需要 ClaimsAuthenticationManager
):
HttpContext ctx; // you need to have access to the context
var claim = new Claim(ClaimTypes.Name, user.Name.Value);
var identity = new ClaimsIdentity(new[] { claim }, "BasicAuthentication"); // this uses basic auth
var principal = new ClaimsPrincipal(identity);
ctx.User = principal;
此示例设置 ClaimsIdentity
,如果您需要添加一个Claim
,您可以这样做那也是。
此 post 可能很长,但包含答案所需的所有相关详细信息。
我一直在搜索,并发现许多其他人也在寻找向 HttpContext 用户添加声明的正确方法,以便在需要时可以使用 Razor 在视图中检索这些声明。
例如,
在默认的 Asp.Net Core 2.0 Web 应用程序中,_LoginPartial 具有显示用户电子邮件的代码。如果我想将其更改为用户全名(这假设注册过程包括名字和姓氏条目,并对 ApplicationUser class)
// Add profile data for application users by adding properties to the ApplicationUser class
public class ApplicationUser : IdentityUser
{
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime DateOfBirth { get; set; }
public Gender Gender { get; set; }
...balance of code removed for brevity
}
我想为用户添加他们的全名和性别的声明,而不是默认应用程序中当前使用的 UserManager 方法。 (还有其他人)
当前默认的 wep 应用代码
@if (SignInManager.IsSignedIn(User))
{
<form asp-area="" asp-controller="Account" asp-action="Logout" method="post" id="logoutForm" class="navbar-right">
<ul class="nav navbar-nav navbar-right">
<li>
<a asp-area="" asp-controller="Manage" asp-action="Index" title="Manage">Hello @UserManager.GetUserName(User)!</a>
</li>
<li>
<button type="submit" class="btn btn-link navbar-btn navbar-link">Log out</button>
</li>
</ul>
</form>
}
else
{
...code removed for brevity
}
我希望完成的事情; 替换这个,
<a asp-area="" asp-controller="Manage" asp-action="Index" title="Manage">Hello @UserManager.GetUserName(User)!</a>
有了这个
<a asp-area="" asp-controller="Manage" asp-action="Index" title="Manage">Hello @((ClaimsIdentity) User.Identity).GetSpecificClaim("avatarUrl")!</a>
注意:GetSpecificClaim 是一种检索声明的扩展方法。
我认为添加声明的最佳位置是在登录方法中。
public async Task<IActionResult> Login(LoginViewModel model, string returnUrl = null)
{
ViewData["ReturnUrl"] = returnUrl;
if (!ModelState.IsValid) return View(model);
// Now model is valid, require the user to have a confirmed email before they can log on.
var user = await _userManager.FindByEmailAsync(model.Email);
if (user != null)
{
if (!await _userManager.IsEmailConfirmedAsync(user))
{
ModelState.AddModelError(string.Empty,
"You must have a confirmed email to log in.");
return View(model);
}
}
else
{
ModelState.AddModelError(string.Empty,
"There is no registered account for the email address supplied.");
return View(model);
}
var result = await _signInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, lockoutOnFailure: true);
if (result.Succeeded)
{
_logger.LogInformation("User logged in.");
// Add claims to signed in user
var userClaims = HttpContext.User.Claims.ToList();
userClaims.Add(new Claim("fullname", user.GetFullName(user.UserName)));
userClaims.Add(new Claim("avatarUrl", user.AvatarUrl));
// Using ClaimsTransformer
// Add claims here for the logged in user using AddUserInfoClaimsAsync extension method
**var ct = new ClaimsHelpers.ClaimsTransformer();
var identityWithInfoClaims = await ct.AddUserInfoClaimsAsync(User, user);**
return RedirectToLocal(returnUrl);
}
if (result.RequiresTwoFactor)
{
return RedirectToAction(nameof(LoginWith2Fa), new { returnUrl, model.RememberMe });
}
if (result.IsLockedOut)
{
_logger.LogWarning("User account locked out.");
return RedirectToAction(nameof(Lockout));
}
ModelState.AddModelError(string.Empty, "Invalid login attempt.");
return View(model);
}
但 userClaims 变量始终为空
问题:
- 为什么刚刚设置的声明列表是空的?
- 是否有不同类型的身份声明?
- 有更好的方法吗?
更新: 我在早期的一些尝试中将 ClaimsTransformer 放在一起,我可以使用它添加声明(请参阅上面登录控制器代码中的粗体更改)但是我现在如何处理 ClaimsPrincipal 变量 identityWithinfoClaims?我不能将 User 设置为等于它,因为 User 是只读的,那么如何正确使用添加了声明的对象?
要添加或转换自定义声明,请实施并使用自定义 ClaimsAuthenticationManager
。 How To: Transform Incoming Claims.
public class ClaimsTransformationModule : ClaimsAuthenticationManager {
public override ClaimsPrincipal Authenticate(string resourceName, ClaimsPrincipal incomingPrincipal) {
if (incomingPrincipal != null && incomingPrincipal.Identity.IsAuthenticated == true) {
var identity = (ClaimsIdentity)incomingPrincipal.Identity;
var user = GetUserData(identity);
identity.AddClaim(new Claim("fullname", user.GetFullName(user.UserName)));
identity.AddClaim(new Claim("avatarUrl", user.AvatarUrl));
}
return incomingPrincipal;
}
}
此处,GetUserData()
根据用户名从数据库中检索用户实体。
在 web.config
中注册此转换器:
<system.identityModel>
<identityConfiguration>
<claimsAuthenticationManager type="MyProject.ClaimsTransformationModule , MyProject, Version=1.0.0.0, Culture=neutral" />
</identityConfiguration>
</system.identityModel>
我们前段时间遇到了完全相同的问题。
解决方案相当简单。您只需要创建自己的 IUserClaimsPrincipalFactory
接口实现并将其注册到 DI 容器中。当然,没有必要从头开始编写该接口的实现 - 您可以从 UserClaimsPrincipalFactory
派生 class 并只覆盖一个方法。
这里是 step-by-step description 包括代码片段。
如果您有 .NET core
中间件管道(或其他自定义设置),您可以在其中处理 authentication
/authorization
并实例化 Claim
,您可以直接添加它像这样 HttpContext
(不需要 ClaimsAuthenticationManager
):
HttpContext ctx; // you need to have access to the context
var claim = new Claim(ClaimTypes.Name, user.Name.Value);
var identity = new ClaimsIdentity(new[] { claim }, "BasicAuthentication"); // this uses basic auth
var principal = new ClaimsPrincipal(identity);
ctx.User = principal;
此示例设置 ClaimsIdentity
,如果您需要添加一个Claim
,您可以这样做那也是。