繁体   English   中英

如何在登录时向 HttpContext 用户添加声明

[英]How to add claims to the HttpContext User on sign in

这篇文章可能很长,但将包含答案所需的所有相关细节。

我一直在搜索,并发现许多其他人也有向 HttpContext 用户添加声明的正确方法,以便可以在需要时使用 Razor 在视图中检索这些声明。

例如,

在默认的 Asp.Net Core 2.0 Web 应用程序中,_LoginPartial 具有显示用户电子邮件的代码。 如果我想将其更改为用户全名(假设注册过程包括名字和姓氏条目,并对 ApplicationUser 类进行适当的更改)

 // 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 方法。 (还有其他人在路上)

当前默认的 web 应用程序代码

@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 变量始终为空

断点显示空的声明列表

问题:

  1. 为什么刚刚设置声明时声明列表是空的?
  2. 是否有不同类型的身份声明?
  3. 有没有更好的方法来做到这一点?

更新:我在之前的一些尝试中将 ClaimsTransformer 放在一起,我可以使用它添加声明(请参阅上面登录控制器代码中以粗体显示的更改)但是我现在如何处理 ClaimsPrincipal 变量 identityWithinfoClaims? 我无法将 User 设置为等于它,因为 User 是只读的,那么如何正确使用添加了声明的对象?

我们前段时间遇到了完全相同的问题。 解决方法相当简单。 您只需要创建自己的IUserClaimsPrincipalFactory接口实现并将其注册到 DI 容器中。 当然,没有必要从头开始编写该接口的实现 - 您可以从UserClaimsPrincipalFactory派生您的类并只覆盖一个方法。

这是包括代码片段的分步说明

要添加或转换自定义声明,请实现并使用自定义ClaimsAuthenticationManager 如何:转换传入声明

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>

如果您有一个.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 ,而不是你能做到这一点。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM