Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/280.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 如何使用ASP.NET核心从DbContext中的JWT获取用户名?_C#_Asp.net Core_.net Core_Jwt_Claims Based Identity - Fatal编程技术网

C# 如何使用ASP.NET核心从DbContext中的JWT获取用户名?

C# 如何使用ASP.NET核心从DbContext中的JWT获取用户名?,c#,asp.net-core,.net-core,jwt,claims-based-identity,C#,Asp.net Core,.net Core,Jwt,Claims Based Identity,在MyDbContext中,我有方法LogChanges,它使用以下信息记录我的logs表中的任何更改: TableName = entityName, IDRow = JsonConvert.SerializeObject(primaryKeys), Value = JsonConvert.SerializeObject(values), Date = dateTimeNow, Author = userFromJWT 我想将Author设置为User,这是由JWT授权的。根据本部分内容:

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

TableName = entityName,
IDRow = JsonConvert.SerializeObject(primaryKeys),
Value = JsonConvert.SerializeObject(values),
Date = dateTimeNow,
Author = userFromJWT
我想将Author设置为User,这是由JWT授权的。根据本部分内容:

“sub”:“我的用户名”

如何在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();
      //...
    }
// ...
private readonly IHttpContextAccessor _httpContext;

public MyDbContext(DbContextOptions options, IHttpContextAccessor httpContext) : base(options) {
  _httpContext = httpContext;
}
//..
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
MyDbContext.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();
      //...
    }
// ...
private readonly IHttpContextAccessor _httpContext;

public MyDbContext(DbContextOptions options, IHttpContextAccessor httpContext) : base(options) {
  _httpContext = httpContext;
}
//..
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
从我使用的JWT的索赔(从“sub”)中获取名称

_httpContext.HttpContext.User.Claims.SingleOrDefault(
        c => c.Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier")?.Value

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

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

  • 您必须添加
    IHttpContextAccessor
    服务。出于性能原因,默认情况下不包括它。(这并不是说它对性能有严重影响。只是如果您不需要它,您可以通过不包含它来获得更高的性能。ASP.NET Core只包含您需要包含的内容。)总之:

    ASP.NET核心2.1

    services.AddHttpContextAccessor();
    
    以前的版本

       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();
          //...
        }
    
    // ...
    private readonly IHttpContextAccessor _httpContext;
    
    public MyDbContext(DbContextOptions options, IHttpContextAccessor httpContext) : base(options) {
      _httpContext = httpContext;
    }
    //..
    
    services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
    
    services.AddSingleton();
    
  • 无论在何处注入,都需要将其作为请求管道的一部分,否则,
    HttpContext
    将不存在。这不应该是一个问题,因为无论如何,你都依赖于JWT的存在。请记住,您不能在常规的控制台应用程序中使用此功能,等等


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

    然后在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();
    }
    
    公共IServiceProvider配置服务(IServiceCollection服务)
    {
    …服务注册部分已注册
    var builder=new ContainerBuilder();
    建造商。填充(服务);
    builder.Register(上下文=>
    {
    var identityUser=context.Resolve()?.HttpContext?.User;
    var userInfo=new userInfo()
    {
    Name=//从identityUser.Claims获取它
    Id=//从identityUser.Claims获取它
    }
    返回用户信息;
    }).AsSelf()
    .InstancePerLifetimeScope();
    }
    
    然后在DbContext中,您可以看到这样的情况(这里我使用的是Autofac IOC容器,但是任何可以注册工厂的容器都可以这样做,如StructureMap、Ninject、Autofac……):

    公共类MyDbContext:DbContext
    {
    私有只读Func_userInfoFactory;
    私有UserInfo UserInfo=>\u userInfoFactory();
    公共MyDbContext(DbContextOptions,Func userInfoFactory):基本(选项)
    {
    这是.\u userInfoFactory=userInfoFactory;
    }
    公共方法()
    {
    var someEntity=new someEntity()
    {
    ChangedByUserId=this.UserInfo.Id
    ...
    }
    }  
    }
    

    这是一个更干净的解决方案,可以在项目之间实现更多的解耦。

    添加到Startup.cs ConfigureServices方法中

    services.AddHttpContextAccessor();
    
    在您的存储库中,在构造函数中使用依赖项注入来添加IHttpContentAccessor,您可以从声明中获取用户ID

    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;
                }           
            }
    
    公共模型存储库(DataContext DataContext、ILogger记录器、IHttpContextAccessor httpContextAccessor)
    {
    _dataContext=dataContext??抛出新的ArgumentNullException(nameof(dataContext));
    _记录器=记录器;
    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);
                }           
            }
    
    公共模型存储库(DataContext DataContext、ILogger记录器、IHttpContextAccessor httpContextAccessor、UserManager UserManager)
    {
    _dataContext=dataContext??抛出新的ArgumentNullException(nameof(dataContext));
    _记录器=记录器;
    if(httpContextAccessor.HttpContext.User.Identity.IsAuthenticated)
    {
    userId=httpContextAccessor.HttpContext.User.FindFirst(ClaimTypes.NameIdentifier).Value;
    user=await userManger.FindByIdAsync(userId);
    }           
    }
    
    假设您的DBContext层(项目)与控制器分离,那么您可以使用DI注入IUserProvider(或任何其他名称),它应该从JWT令牌读取用户详细信息。此外,您可以直接从Claims/HttpContext获取用户详细信息,而不是从Jwt令牌获取。现在,您的DbContext对IHttpContextAccessor有完全不必要的依赖。您应该只依赖于您需要的,在这种情况下,您只需要ClaimsPrincipal(或IPrincipal)。子索赔并不总是第一个索赔。好吧,我已经改变了我的LINQ。谢谢将可选的
    IPrincipal
    注入DbC不是更好吗