如何在 .NET Core Web API 中获取当前用户(来自 JWT 令牌)

How do I get current user in .NET Core Web API (from JWT Token)

经过大量努力(以及大量教程、指南等)后,我设法设置了一个小型 .NET Core REST Web API,当存储的用户名和密码为有效。

令牌将用户 ID 存储为子声明。

我还设法设置 Web API 以在方法使用 Authorize 注释时验证这些令牌。

 app.UseJwtBearerAuthentication(...)

现在我的问题是: 如何在我的控制器(在 Web API)中读取用户 ID(存储在主题声明中)?

基本上就是这个问题 (),但我需要一个网络答案 api。而且我没有 UserManager。所以我需要从某个地方阅读主题声明。

你可以使用这个方法:

var email = User.FindFirst("sub")?.Value;

在我的例子中,我将电子邮件用作唯一值

已接受的答案对我不起作用。我不确定这是我使用 .NET Core 2.0 还是其他原因造成的,但看起来框架将 Subject Claim 映射到 NameIdentifier 声明。所以,以下对我有用:

string userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;

请注意,这假设主题 sub 声明已在 JWT 中设置,其值是用户的 ID。

默认情况下,.NET 中的 JWT 身份验证处理程序会将 JWT 访问令牌的子声明映射到 System.Security.Claims.ClaimTypes.NameIdentifier 声明类型。 [Source]

还有一个 discussion thread on GitHub 他们认为这种行为令人困惑。

如果您使用 Name 在此处存储 ID

var tokenDescriptor = new SecurityTokenDescriptor
{
    Subject = new ClaimsIdentity(new Claim[]
                {
                    new Claim(ClaimTypes.Name, user.Id.ToString())
                }),
    Expires = DateTime.UtcNow.AddDays(7),
    SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};

在每个控制器方法中,您可以通过以下方式获取当前用户的 ID:

var claimsIdentity = this.User.Identity as ClaimsIdentity;
var userId = claimsIdentity.FindFirst(ClaimTypes.Name)?.Value;

您可以使用。

User.Identity.Name

似乎很多人都在看这个问题,所以我想分享一些我前阵子问这个问题后学到的更多信息。 它使一些事情变得更加清晰(至少对我而言)并且不那么明显(对我这个 .NET 新手来说)。

正如评论中提到的 Marcus Höglund

It should be the same for "web api"..In ASP.NET Core Mvc and Web Api are merged to use the same controller.

绝对正确。


因为 .NET 和 .NET Core 都是一样的。

比我刚接触 .NET Core 和整个 .NET 世界还早。重要的缺失信息是,在 .NET 和 .NET Core 中,所有身份验证都可以缩减到 System.Security.Claims 命名空间及其 ClaimsIdentity、ClaimsPrinciple 和 Claims.Properties。因此,它在 .NET Core 控制器类型(API 和 MVC 或 Razor 或...)中使用,并且可通过 HttpContext.User.

访问

一个重要的旁注,所有教程都没有讲到。

因此,如果您开始在 .NET 中使用 JWT 令牌做一些事情,请不要忘记也对 ClaimsIdentity, ClaimsPrinciple and Claim.Properties 充满信心。就是这样。现在你知道了。 Heringer 在其中一条评论中指出了这一点。


ALL 基于声明的身份验证中间件(如果正确实施)将使用身份验证期间收到的声明填充 HttpContext.User

据我所知,这意味着可以放心地相信 HttpContext.User 中的值。 但稍等 就会知道选择中间件时要注意什么。有很多不同的认证 中间件已经可用(除了 .UseJwtAuthentication())。

使用小型自定义扩展方法,您现在可以像那样获取当前用户 ID(主题声明更准确)

 public static string SubjectId(this ClaimsPrincipal user) { return user?.Claims?.FirstOrDefault(c => c.Type.Equals("sub", StringComparison.OrdinalIgnoreCase))?.Value; }

或者你使用Ateik的答案中的版本。


等等:有一件奇怪的事

接下来让我感到困惑的是:根据 OpenID Connect 规范,我正在寻找 "sub" 声明(当前用户)但找不到。就像 Honza Kalfus 在他的回答中做不到。

为什么?

因为微软 "sometimes" "a bit" 与众不同。或者至少他们做了更多(和意想不到的)事情。例如原始问题中提到的官方 Microsoft JWT Bearer 身份验证中间件。 Microsoft 决定在其所有官方身份验证中间件中转换声明(声明的名称)(出于兼容性原因我不知道更详细)。

您不会找到 "sub" 声明(尽管它是 OpenID Connect 指定的唯一声明)。因为它被转换为 these fancy ClaimTypes。这还不错,如果您需要将不同的声明映射到唯一的内部名称,它允许您添加映射。

您要么坚持使用 Microsoft 命名(并且当您 add/use 是非 Microsoft 中间件时必须注意这一点),要么您了解如何为 Microsoft 中间件转换声明映射。

在 JwtBearerAuthentication 的情况下完成(在启动早期或至少在添加中间件之前完成):

JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();

如果您想坚持主题声明中的 Microsoft 命名(别打我,我现在不确定 Name 是否是正确的映射):

    public static string SubjectId(this ClaimsPrincipal user) { return user?.Claims?.FirstOrDefault(c => c.Type.Equals(ClaimTypes.NameIdentifier, StringComparison.OrdinalIgnoreCase))?.Value; }

请注意,其他答案使用更高级、更方便的FindFirst方法。虽然我的代码示例显示它没有那些你可能应该使用它们。

因此,您的所有声明都存储在 HttpContext.User 中并可访问(通过一个名称或另一个名称)。


可是我的令牌呢?

我不知道其他中间件,但 JWT Bearer Authentication 允许为每个请求保存令牌。但这需要激活(在StartUp.ConfigureServices(...)。

services
  .AddAuthentication("Bearer")
  .AddJwtBearer("Bearer", options => options.SaveToken = true);

然后可以通过

访问作为字符串(或空值)的实际令牌(以其所有的神秘形式)
HttpContext.GetTokenAsync("Bearer", "access_token")

这个方法有一个旧版本(这在 .NET Core 2.2 中对我有用,没有弃用警告)。

如果您需要从该字符串中解析和提取值,问题 可能会有所帮助。


嗯,希望总结对你也有帮助。

我使用了 HttpContext 并且效果很好:

var email = string.Empty;
if (HttpContext.User.Identity is ClaimsIdentity identity)
{
    email = identity.FindFirst(ClaimTypes.Name).Value;
}

在我的例子中,我在 JWT 令牌生成之前将 ClaimTypes.Name 设置为唯一的用户电子邮件:

claims.Add(new Claim(ClaimTypes.Name, user.UserName));

然后我将唯一的用户 ID 存储到 ClaimTypes.Name标识符:

claims.Add(new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()));

然后在控制器的代码中:

int GetLoggedUserId()
        {
            if (!User.Identity.IsAuthenticated)
                throw new AuthenticationException();

            string userId = User.Claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier).Value;

            return int.Parse(userId);
        }

我在 .net core 5 web 中使用以下代码工作 api

User.Claims.First(x => x.Type == "id").Value;

asp.net核心身份获取用户id

 public async Task<IActionResult> YourMethodName()
{
    var userId =  User.FindFirstValue(ClaimTypes.NameIdentifier) // will give the user's userId
    var userName =  User.FindFirstValue(ClaimTypes.Name) // will give the user's userName

    ApplicationUser applicationUser = await _userManager.GetUserAsync(User);
    string userEmail = applicationUser?.Email; // will give the user's Email
}

.net core identity 获取用户id

 public static class ClaimsPrincipalExtensions
{
    public static T GetLoggedInUserId<T>(this ClaimsPrincipal principal)
    {
        if (principal == null)
            throw new ArgumentNullException(nameof(principal));

        var loggedInUserId = principal.FindFirstValue(ClaimTypes.NameIdentifier);

        if (typeof(T) == typeof(string))
        {
            return (T)Convert.ChangeType(loggedInUserId, typeof(T));
        }
        else if (typeof(T) == typeof(int) || typeof(T) == typeof(long))
        {
            return loggedInUserId != null ? (T)Convert.ChangeType(loggedInUserId, typeof(T)) : (T)Convert.ChangeType(0, typeof(T));
        }
        else
        {
            throw new Exception("Invalid type provided");
        }
    }

    public static string GetLoggedInUserName(this ClaimsPrincipal principal)
    {
        if (principal == null)
            throw new ArgumentNullException(nameof(principal));

        return principal.FindFirstValue(ClaimTypes.Name);
    }

    public static string GetLoggedInUserEmail(this ClaimsPrincipal principal)
    {
        if (principal == null)
            throw new ArgumentNullException(nameof(principal));

        return principal.FindFirstValue(ClaimTypes.Email);
    }
}