如何使用 ASP.NET Core 从 DbContext 中的 JWT 获取用户名?

How to get username from JWT in DbContext using ASP.NET Core?

MyDbContext 中,我有方法 LogChanges,它使用以下信息记录我的 logs table 中的任何更改:

TableName = entityName,
IDRow = JsonConvert.SerializeObject(primaryKeys),
Value = JsonConvert.SerializeObject(values),
Date = dateTimeNow,
Author = userFromJWT

我想将 Author 设置为 JWT 授权的 User。从这部分完全正确:

"sub": "myUserName"

如何在 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

假设您实际上已经集成到 ASP.NET 核心身份验证子系统(即 services.AddAuthenticationapp.UseAuthentication),那么这基本上已经为您处理了。将读取 JWT 以从中构建一个 ClaimsPrincipal 实例,然后将其存储在 HttpContext.User 中。因此,用户的用户名将位于 HttpContext.User.Identity.Name 的标准位置,或者您可以通过 HttpContext.User.Identity 上的 Claims 集合直接访问它(以及任何其他声明)。

如果问题是您需要此信息的某个地方您无法直接访问 HttpContext.User(基本上是控制器或视图之外的任何地方),那么您只需注入 IHttpContextAccessor.这需要两件事:

  1. 您必须添加 IHttpContextAccessor 服务。出于性能原因,默认情况下不包含它。 (这并不是说它对性能有严重的影响。只是如果你不需要它,你可以通过不包含它来获得更多的性能。ASP.NET Core is all about including only你需要包括什么。)无论如何:

    ASP.NET核心2.1

    services.AddHttpContextAccessor();
    

    以前的版本

    services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
    
  2. 无论你在哪里注入,它都需要成为请求管道的一部分,否则,HttpContext 将不存在。这应该不是问题,因为无论如何你都依赖于 JWT 的存在。请记住,您不能在常规控制台应用等中使用它。

是的,我在@Chris Prat 的解决方案中看到的唯一问题是您现在需要在实际上与它无关的项目中引用 Asp.Net.Core 程序集。 对我来说,更好的解决方案是定义一个具有所需属性的新 class。然后使用 DI/IOC 将其注册为 Func 并将其传递给 DBContext。 即

public class UserInfo
{
    public Guid UserId{get;set;}
    public string UserName{get;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
           ...
        }
     }  
}

这是一种更简洁的解决方案,可以使项目之间更加分离。

添加到您的 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);
            }           
        }