如何使用 ASP.NET Core 从 DbContext 中的 JWT 获取用户名?
Posted
技术标签:
【中文标题】如何使用 ASP.NET Core 从 DbContext 中的 JWT 获取用户名?【英文标题】:How to get username from JWT in DbContext using ASP.NET Core? 【发布时间】:2018-11-23 00:46:00 【问题描述】:在 MyDbContext 中,我有方法 LogChanges,它使用以下信息记录我的 logs 表中的任何更改:
TableName = entityName,
IDRow = JsonConvert.SerializeObject(primaryKeys),
Value = JsonConvert.SerializeObject(values),
Date = dateTimeNow,
Author = userFromJWT
我想将 Author 设置为 JWT 授权的 User。从这部分确切地说:
“子”:“我的用户名”
如何在 MyDbContext 中获取该用户名?也许是某种依赖注入?
提前致谢!
@解决方案
Startup.cs
public void ConfigureServices(IServiceCollection services)
// ...
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
options.TokenValidationParameters = new TokenValidationParameters
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = Configuration["Jwt:Issuer"],
ValidAudience = Configuration["Jwt:Issuer"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Jwt:Key"]))
;
);
services.AddHttpContextAccessor();
//...
MyDbContext.cs
// ...
private readonly IHttpContextAccessor _httpContext;
public MyDbContext(DbContextOptions options, IHttpContextAccessor httpContext) : base(options)
_httpContext = httpContext;
//..
并从我使用的 JWT 的声明(来自“sub”)中获取名称
_httpContext.HttpContext.User.Claims.SingleOrDefault(
c => c.Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier")?.Value
【问题讨论】:
鉴于您的 DBContext 层(项目)与 Controller 分离,那么您可以使用 DI 注入 IUserProvider(或任何其他名称),它应该从 JWT 令牌中读取用户详细信息。此外,您可以直接从 Claims/HttpContext 中获取用户详细信息,而不是从 Jwt 令牌中获取。 现在您的 DbContext 对 IHttpContextAccessor 具有完全不必要的依赖。你应该只依赖于你需要的东西,在这种情况下你只需要 ClaimsPrincipal (或 IPrincipal )。并且 sub 声明并不总是集合中的第一个声明。 好的,我已经更改了我的 LINQ。谢谢! 【参考方案1】:假设您实际上已集成到 ASP.NET Core 身份验证子系统(即 services.AddAuthentication
和 app.UseAuthentication
)中,那么这基本上是为您处理的。将读取 JWT 以从中构建一个 ClaimsPrincipal
实例,然后将其存储在 HttpContext.User
中。因此,用户的用户名将位于HttpContext.User.Identity.Name
的标准位置,或者您可以通过HttpContext.User.Identity
上的Claims
集合直接访问它(以及任何其他声明)。
如果问题是您在无法直接访问HttpContext.User
的某个地方(基本上是控制器或视图之外的任何地方)需要此信息,那么您只需注入IHttpContextAccessor
。这需要两件事:
您必须添加IHttpContextAccessor
服务。出于性能原因,默认情况下不包含它。 (这并不是说它对性能有严重影响。只是如果你不需要它,你可以通过不包含它来获得更高的性能。ASP.NET Core 就是只包含你需要的东西需要包括。)无论如何:
ASP.NET Core 2.1
services.AddHttpContextAccessor();
以前的版本
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
无论你在哪里注入它都需要成为请求管道的一部分,否则HttpContext
将不存在。这不应该是一个问题,因为无论如何你都依赖于 JWT 的存在。请记住,您不能在常规控制台应用等中使用它。
【讨论】:
将可选的IPrincipal
注入DbContext 不是更好吗?这样您就不必依赖 HttpContext 并且 DbContext 将可以在控制台应用程序中使用并且更容易测试。【参考方案2】:
是的,我在@Chris Prat 的解决方案中看到的唯一问题是,您现在需要在实际上与之无关的项目中引用 Asp.Net.Core 程序集。 对我来说,更好的解决方案是定义一个具有所需属性的新类。然后使用 DI/IOC 将其注册为 Func 并将其传递给 DBContext。 即。
public class UserInfo
public Guid UserIdget;set;
public string UserNameget;set;
然后在 Startup.cs 中执行以下操作:
public IServiceProvider ConfigureServices(IServiceCollection services)
... services registration part ommited
var builder = new ContainerBuilder();
builder.Populate(services);
builder.Register(context=>
var identityUser = context.Resolve<IHttpContextAccessor>()?.HttpContext?.User;
var userInfo = new UserInfo()
Name=//get it from identityUser.Claims
Id= //get it from identityUser.Claims
return userInfo;
).AsSelf()
.InstancePerLifetimeScope();
然后在 DbContext 你有这个(这里我使用 Autofac IOC 容器,但是任何可以注册工厂的容器都可以像 StructureMap、Ninject、Autofac...):
public class MyDbContext: DbContext
private readonly Func<UserInfo> _userInfoFactory;
private UserInfo UserInfo => _userInfoFactory();
public MyDbContext(DbContextOptions options, Func<UserInfo> userInfoFactory) : base(options)
this._userInfoFactory = userInfoFactory;
public void SomeMethod()
var someEntity = new SomeEntity()
ChangedByUserId = this.UserInfo.Id
...
这是一种更简洁的解决方案,可以让项目之间更加解耦。
【讨论】:
【参考方案3】:添加到您的 Startup.cs ConfigureServices 方法
services.AddHttpContextAccessor();
在您的存储库中使用构造函数中的依赖注入来添加 IHttpContentAccessor,您可以从声明中获取 UserId
public ModelRepository(DataContext dataContext, ILogger<ModelRepository> logger, IHttpContextAccessor httpContextAccessor)
_dataContext = dataContext ?? throw new ArgumentNullException(nameof(dataContext));
_logger = logger;
if(httpContextAccessor.HttpContext.User.Identity.IsAuthenticated)
userId = httpContextAccessor.HttpContext.User.FindFirst(ClaimTypes.NameIdentifier).Value;
如果您需要用户提供更多信息,您也可以注入 UserManager
public ModelRepository(DataContext dataContext, ILogger<ModelRepository> logger, IHttpContextAccessor httpContextAccessor, UserManager<ApplicationUser> userManager)
_dataContext = dataContext ?? throw new ArgumentNullException(nameof(dataContext));
_logger = logger;
if(httpContextAccessor.HttpContext.User.Identity.IsAuthenticated)
userId = httpContextAccessor.HttpContext.User.FindFirst(ClaimTypes.NameIdentifier).Value;
user = await userManger.FindByIdAsync(userId);
【讨论】:
以上是关于如何使用 ASP.NET Core 从 DbContext 中的 JWT 获取用户名?的主要内容,如果未能解决你的问题,请参考以下文章
如何从 ASP.NET MVC 到 ASP.NET Core Web API 的 PUT?
使用命令行如何在 ASP.NET 中首先从 DB 搭建脚手架 - 而不是 ASP.NET Core MVC?