经过大量的努力(以及大量的tuturials,指南等)后,我设法设置了一个小的.NET Core REST Web API,当存储的用户名和密码有效时,Auth Controller会发出JWT令牌。
令牌将用户ID存储为子声明。
当方法使用授权注释时,我还设法设置Web API来验证这些令牌。
app.UseJwtBearerAuthentication(...)
现在我的问题: 如何在控制器中(在Web API中)读取用户ID(存储在主题声明中)?
基本上这个问题(How do I get current user in ASP .NET Core)但是我需要一个web api的答案。我没有UserManager。所以我需要从某个地方阅读主题索赔。
答案 0 :(得分:21)
接受的答案对我不起作用。我不确定这是由我使用.NET Core 2.0还是由其他原因造成的,但看起来该框架将主题声明映射到NameIdentifier声明。所以,以下对我有用:
string userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
请注意,这假设在JWT中设置了主题sub
声明,其值是用户的ID。
默认情况下,.NET中的JWT身份验证处理程序会将JWT访问令牌的子声明映射到System.Security.Claims.ClaimTypes.NameIdentifier
声明类型。 [Source]
还有一个discussion thread on GitHub,他们认为这种行为令人困惑。
答案 1 :(得分:13)
您可以使用此方法:
var email = User.FindFirst("sub")?.Value;
就我而言,我使用电子邮件作为唯一值
答案 2 :(得分:5)
如果您使用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;
答案 3 :(得分:4)
似乎很多人都在看这个问题,所以我想分享一些自从我问这个问题以来学到的更多信息。 它使某些事情更清楚(至少对我来说),但并不那么明显(对我来说是.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规范,我正在寻找“子”声明(当前用户),但找不到它。就像 Honza Kalfus 不能回答那样。
为什么?
因为Microsoft“有时”“有点”不同。或者至少他们做了更多(和意想不到的)事情。例如,原始问题中提到的官方Microsoft JWT Bearer身份验证中间件。 微软决定在其所有官方身份验证中间件中转换声明(声明的名称)(出于兼容性原因,我不知道详细信息。)
您不会找到“次级”声明(尽管它是OpenID Connect指定的单个声明)。因为它已转换为these fancy ClaimTypes。这并不全是坏事,它允许您在需要将不同的声明映射到唯一的内部名称时添加映射。
要么坚持使用Microsoft命名(并且要记住,当添加/使用非Microsoft中间件时),或者您发现了如何为Microsoft中间件打开声明映射。
对于JwtBearerAuthentication,它已完成(在StartUp的早期或至少在添加中间件之前进行):
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承载身份验证允许为每个请求保存令牌。但这需要激活(在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这个问题可能会有所帮助。
好吧,希望摘要对您有帮助。
答案 4 :(得分:3)
我使用了HttpContext,它运行良好:
var email = string.Empty;
if (HttpContext.User.Identity is ClaimsIdentity identity)
{
email = identity.FindFirst(ClaimTypes.Name).Value;
}
答案 5 :(得分:1)
在我的情况下,我在生成JWT令牌之前将ClaimTypes.Name设置为唯一的用户电子邮件:
claims.Add(new Claim(ClaimTypes.Name, user.UserName));
然后我将唯一的用户ID存储到ClaimTypes.NameIdentifier:
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);
}
答案 6 :(得分:1)
我在 .net core 5 web api 中使用以下代码工作
User.Claims.First(x => x.Type == "id").Value;
答案 7 :(得分:0)
您可以使用。
User.Identity.Name