经过大量的努力(以及许多tuturials,指南等)后,我设法设置了一个小的.NET Core REST Web API,当存储的用户名和密码有效时,Auth Controller会发出JWT令牌。
令牌将用户ID存储为子声明。
当方法使用Authorize注释时,我还设法设置Web API来验证这些令牌。
app.UseJwtBearerAuthentication(...)
现在我的问题是:如何在我的控制器中(在Web API中)读取用户ID(存储在主题声明中)?
这基本上是这个问题(How do I get current user in ASP .NET Core),但我需要一个web api的答案。我没有UserManager。所以我需要从某个地方阅读主题索赔。
您可以使用此方法:
var email = User.FindFirst("sub")?.Value;
在我的情况下,我使用电子邮件作为一个独特的价值
接受的答案对我不起作用。我不确定这是由我使用.NET Core 2.0还是由其他东西引起的,但看起来该框架将Subject Claim映射到NameIdentifier声明。所以,以下对我有用:
string userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
请注意,这假定在JWT中设置了主题sub
声明,其值是用户的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;
似乎很多人都在关注这个问题,所以我想分享一些我学习的信息,因为我不久前问过这个问题。它使一些事情变得更加清晰(至少对我来说)并且不那么明显(对我来说就像.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令牌,请不要忘记对ClaimsIdentity,ClaimsPrinciple和Claim.Properties充满信心。这就是全部。现在你知道了。 Heringer在其中一条评论中指出了这一点。
所有基于声明的身份验证中间件(如果正确实现)将使用在身份验证期间收到的声明填充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在答案中所做的那样。
为什么?
因为微软“有时”“有点”不同。或者至少他们会做更多(和意外)的事情。例如,原始问题中提到的官方Microsoft JWT Bearer身份验证中间件。 Microsoft决定在其所有官方身份验证中间件中转换声明(声明的名称)(出于兼容性原因,我不知道更详细)。
您将找不到“子”声明(尽管它是OpenID Connect指定的单个声明)。因为它被转换为these fancy ClaimTypes。这并非全是坏事,如果您需要将不同的声明映射到唯一的内部名称,它允许您添加映射。
您要么坚持使用Microsoft命名(并且必须记住,当您添加/使用非Microsoft中间件时)或者您发现如何为Microsoft中间件转换声明映射。
在JwtBearerAuthentication的情况下,它完成(在StartUp的早期或至少在添加中间件之前):
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
如果你想坚持主题声称的微软命名(不要打败我,如果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承载认证允许为每个请求保存令牌。但这需要激活(在StartUp.ConfigureServices(...
)。
services
.AddAuthentication("Bearer")
.AddJwtBearer("Bearer", options => options.SaveToken = true);
然后可以通过访问实际的令牌(以其所有的神秘形式)作为字符串(或null)
HttpContext.GetTokenAsync("Bearer", "access_token")
这个方法有一个旧版本(这在.NET Core 2.2中适用于我,没有弃用警告)。
如果您需要解析并从此字符串中提取值,可能How to decode JWT token的问题会有所帮助。
好吧,我希望这个总结对你也有帮助。
你可以这样做。
user.identity.name