如何使用 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.AddAuthenticationapp.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?

如何从控制器告诉 ASP.NET Core 视图存在?

如何从 ASP.NET Core WebAPI 获取 int 值?

如何使用 ASP.NET Core 从查询字符串中读取值?

如何使用 ASP.Net Core Identity 从登录用户中检索 Google 个人资料图片?