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

mon*_*nty 32 jwt asp.net-core-webapi asp.net-core-1.1

经过大量的努力(以及许多tuturials,指南等)后,我设法设置了一个小的.NET Core REST Web API,当存储的用户名和密码有效时,Auth Controller会发出JWT令牌.

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

当方法使用Authorize注释时,我还设法设置Web API来验证这些令牌.

 app.UseJwtBearerAuthentication(...)

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

基本上这个问题(我如何在ASP .NET Core中获得当前用户)但我需要一个web api的答案.我没有UserManager.所以我需要从某个地方阅读主题索赔.

Hon*_*fus 38

接受的答案对我不起作用.我不确定这是由我使用.NET Core 2.0还是由其他东西引起的,但看起来该框架将Subject Claim映射到NameIdentifier声明.所以,以下对我有用:

string userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
Run Code Online (Sandbox Code Playgroud)

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

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

在GitHub上还有一个讨论主题,他们认为这种行为令人困惑.

  • 或者稍微短一些: [`User.FindFirstValue(ClaimTypes.NameIdentifier)`](https://docs.microsoft.com/en-us/dotnet/api/system.security.claims.principalextensions.findfirstvalue?view=aspnetcore-2.2 ) (3认同)
  • User 是 `ClaimsPrincipal` 的一个实例。通常,您可以从 HttpContext 检索它,请参阅 https://learn.microsoft.com/en-us/dotnet/api/system.security.claims.claimsprincipal?view=net-6.0 (2认同)

Ate*_*eik 22

您可以使用此方法:

var email = User.FindFirst("sub")?.Value;
Run Code Online (Sandbox Code Playgroud)

在我的情况下,我使用电子邮件作为一个独特的价值

  • 另一种方式可能是:string sub = HttpContext?.User.Claims.FirstOrDefault(c => c.Type == System.Security.Claims.ClaimTypes.NameIdentifier).Value; (4认同)
  • 谢谢,应标记为已接受答案!对于用户名:User.Identity.Name。用户是Microsoft.AspNetCore.Mvc.ControlerBase的属性,类型是System.Security.Claims.ClaimsPrincipal。只是添加。 (2认同)
  • 请注意,如果您不使用[Authorize]属性,则该User可能是一种空用户,其中User.Identity.IsAuthenticated为false。所以要当心。 (2认同)

Imp*_*erC 11

如果您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)
};
Run Code Online (Sandbox Code Playgroud)

在每种控制器方法中,您可以通过以下方法下载当前用户的ID:

var claimsIdentity = this.User.Identity as ClaimsIdentity;
var userId = claimsIdentity.FindFirst(ClaimTypes.Name)?.Value;
Run Code Online (Sandbox Code Playgroud)


mon*_*nty 7

似乎很多人都在看这个问题,所以我想分享一些自从我问这个问题以来学到的更多信息。它使某些事情变得更清晰(至少对我而言),但并没有那么明显(对我来说是.NET新手)。

正如MarcusHöglund在评论中提到的那样:

对于“ Web api”应该相同。.在ASP.NET Core中,Mvc和Web Api合并为使用同一控制器。

这绝对是正确的,也是绝对正确的。


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

那时我还不是.NET Core和整个.NET世界的新手。缺少的重要信息是,在.NET和.NET Core中,所有身份验证都可以使用ClaimsIdentity,ClaimsPrinciple和Claims.Properties 缩减为System.Security.Claims命名空间。因此,它可以在.NET Core两种控制器类型(API和MVC或Razor或...)中使用,并且可以通过访问HttpContext.User

一个重要的旁注是所有教程都未能说明。

因此,如果您开始使用.NET中的JWT令牌进行操作,请不要忘记也对ClaimsIdentityClaimsPrincipleClaim.Properties充满信心。这就是全部。现在您知道了。Heringer在其中一项评论中指出了这一点。


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

据我现在的理解,这意味着人们可以放心地相信HttpContext.User但是请稍等一下,以了解选择中间件时的介意。除了之外,已经有很多不同的身份验证中间件可用.UseJwtAuthentication()

With small custom extension methods you can now get the current user id (more accurate the subject claim) like that

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

Or you use the version in the answer of Ateik.


BUT WAIT: there is one strange thing

The next thing that confused me back than: according to the OpenID Connect spec I was looking for "sub" claim (the current user) but couldn't find it. Like Honza Kalfus couldn't do in his answer.

Why?

Because Microsoft is "sometimes" "a bit" different. Or at least they do a bit more (and unexpected) things. For example the official Microsoft JWT Bearer authentication middleware mentioned in the original question. Microsoft decided to convert claims (the names of the claims) in all of their official authentication middleware (for compatibility reasons I don't know in more detail).

You won't find a "sub" claim (though it is the single one claim specified by OpenID Connect). Because it got converted to these fancy ClaimTypes. It's not all bad, it allows you to add mappings if you need to map different claims into a unique internal name.

Either you stick with the Microsoft naming (and have to mind that when you add/use a non Microsoft middleware) or you find out how to turn the claim mapping of for the Microsoft middleware.

In case of the JwtBearerAuthentication it is done (do it early in StartUp or at least before adding the middleware):

JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
Run Code Online (Sandbox Code Playgroud)

If you want to stick with the Microsoft namings the subject claim (don't beat me, I am not sure right now if Name is the correct mapping):

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

Note that the other answers use the more advanced and way more convenient FindFirst method. Though my code samples show it without those you may should go with them.

So all your claims are stored and accessible (via one name or the other) in the HttpContext.User.


But where is my token?

I don't know for the other middleware but the JWT Bearer Authentication allows to save the token for each request. But this needs to be activated (in StartUp.ConfigureServices(...).

services
  .AddAuthentication("Bearer")
  .AddJwtBearer("Bearer", options => options.SaveToken = true);
Run Code Online (Sandbox Code Playgroud)

The actual token (in all it's cryptic form) as string (or null) can then be accessed via

HttpContext.GetTokenAsync("Bearer", "access_token")
Run Code Online (Sandbox Code Playgroud)

There has been an older version of this method (this works for me in .NET Core 2.2 without deprecated warning).

If you need to parse and extract values from this string may the question How to decode JWT token helps.


Well, I hope that summary helps you too.


小智 6

你可以使用。

用户.身份.名称


War*_*red 6

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

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


nik*_*iuk 5

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

claims.Add(new Claim(ClaimTypes.Name, user.UserName));
Run Code Online (Sandbox Code Playgroud)

然后我将唯一的用户 ID 存储到 ClaimTypes.NameIdentifier:

claims.Add(new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()));
Run Code Online (Sandbox Code Playgroud)

然后在控制器的代码中:

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

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

            return int.Parse(userId);
        }
Run Code Online (Sandbox Code Playgroud)