JWT身份验证 - UserManager.GetUserAsync返回null

31

在认证时,我在AuthController中创建了一些声明(Claims),其中之一是UserID

...
Subject = new ClaimsIdentity(new[]
{
  new Claim(ClaimTypes.Name, user.UserName),
  new Claim("UserID", user.Id.ToString()),
})

当 Angular 应用发出请求时,我可以在另一个控制器中获取 UserID

Claim claimUserId = User.Claims.SingleOrDefault(c => c.Type == "UserID");

ControllerBase.User实例包含.Identity对象,该对象再包含Claims集合。

  • Identity.IsAuthenticated等于True

  • Identity.Name包含admin字符串(相关用户的名称)。

如果我这样尝试获取用户:

var user = await UserManager.GetUserAsync(HttpContext.User)

user为空。

也许我忘记添加一些额外的声明?

或者,一旦我使用JWT - 我应该重写默认的UserManager功能,以便它通过claim获取用户并保存UserID

或者可能有更好的方法?


附加信息:

注册了如下Identity

services.AddIdentity<ApplicationUser, ApplicationRole>()
    .AddEntityFrameworkStores<AppDbContext>()
    .AddDefaultTokenProviders();

ApplicationUser.Id 字段是 bigint 类型(或在 C# 中是 long 类型)

同时,我使用通过 ServiceProvider 解析的 UserManager 在 EF Seed Data 中创建用户。

_userManager = scope.ServiceProvider.GetService<UserManager<ApplicationUser>>();
    ...
        adminUser.PasswordHash = new PasswordHasher<ApplicationUser>().HashPassword(adminUser, "123qwe");
        _userManager.CreateAsync(adminUser);

你能分享一下如何注册Identity吗? - Alex Riabov
嗨@AlexRiabov,我刚回答了你的问题,在“附加信息”部分下的代码。 - Alex Herman
3个回答

54

UserManager.GetUserAsync 内部使用 UserManager.GetUserId 来检索用户的用户 id,然后使用它从用户存储中查询对象 (即您的数据库)。

GetUserId 基本上看起来像这样:

public string GetUserId(ClaimsPrincipal principal)
{
    return principal.FindFirstValue(Options.ClaimsIdentity.UserIdClaimType);
}

这将返回Options.ClaimsIdentity.UserIdClaimType的声明值。 Options是你用来配置身份验证的IdentityOptions对象。默认情况下,UserIdClaimType的值为ClaimTypes.NameIdentifier,即"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier"

因此,当你尝试使用 UserManager.GetUserAsync(HttpContext.User) 时,如果该用户主体具有UserID声明,则用户管理器只是在寻找不同的声明。

你可以通过切换到 ClaimTypes.NameIdentifier 来解决这个问题:

new ClaimsIdentity(new[]
{
    new Claim(ClaimTypes.Name, user.UserName),
    new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
})

或者您可以正确配置身份验证,使其使用您的UserID声明类型:

// in Startup.ConfigureServices
services.AddIdentity(options => {
    options.ClaimsIdentity.UserIdClaimType = "UserID";
});

1
或者,如果userId不可用,可以根据业务需求使用userManager.FindByNameAsync(username) - Rosdi Kasim
仅使用new Claim(ClaimTypes.NameIdentifier, user.Id)就可以了。之前使用的是user.UserName,没有意识到“名称标识符”会是一个ID。谁能想到呢。 - mwilson
@mwilson,你如何使用声明主要取决于你的应用程序。有不同的方法来处理这个问题,它也取决于你的ID是什么。 - poke
很棒的解决方案。在我的情况下,Startup.ConfigureServices解决方案对我有用。在控制器中设置断点并浏览User->claims帮助我意识到我的组织正在使用哪种声明类型来获取用户ID。然后设置options.ClaimsIdentity.UserIdClaimType = ClaimTypes.[option]就可以了。 - eaglei22
UserManager.GetUserAsync 最终会调用 IUserStore 实现的 FindByIdAsync 方法,如果您需要调试自定义的 IUserStore 实现,这是非常有帮助的信息。 - ackh

2
对我来说,这是一篇教程给出了错误的建议,它将JWT令牌的“sub”声明分配了一个“通用描述”。
在JWT身份验证库中,“ClaimTypes.NameIdentifier”的声明条目会自动从“sub”声明中创建,因此最终导致了重复。一个带有错误值,一个带有正确值。
以下是不正确的实现:
   private Claim[] CreateClaims(IdentityUser user)
    {
        return new[] {
            new Claim(ClaimTypes.NameIdentifier, user.Id),
            new Claim(ClaimTypes.Name, user.UserName),
            new Claim(ClaimTypes.Email, user.Email),
            new Claim(JwtRegisteredClaimNames.Sub, _configuration["Jwt:Subject"]),
            new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
            new Claim(JwtRegisteredClaimNames.Iat, DateTime.UtcNow.ToString()),
        };
    }

这是正确的实现方式:

private Claim[] CreateClaims(IdentityUser user)
{
    return new[] {
        new Claim(ClaimTypes.Name, user.UserName),
        new Claim(ClaimTypes.Email, user.Email),
        new Claim(JwtRegisteredClaimNames.Sub, user.Id),
        new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
        new Claim(JwtRegisteredClaimNames.Iat, DateTime.UtcNow.ToString()),
    };
}

正确实现后,索赔的外观如何:

Claims


我一定是跟着同一个教程做的!非常感谢你在解决问题后发布出来!删除名称标识符并将 Sub 值替换为 userId 对我解决了问题! - James
我一定是跟着同一个教程做的!非常感谢你在解决问题后的发布!删除名称标识符并将 Sub 值替换为用户ID对我解决了问题! - undefined

2
当您创建“Claims”时,只需像这样操作:

 List<Claim> claims = new()
 {
    new Claim(ClaimTypes.NameIdentifier, user.Id),  // This line is important.
    new Claim(ClaimTypes.Email, user.Email),
    new Claim(JwtRegisteredClaimNames.Jti, jti)
 };

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接