C# 如何使用ASP.NET核心从DbContext中的JWT获取用户名?
在MyDbContext中,我有方法LogChanges,它使用以下信息记录我的logs表中的任何更改: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授权的。根据本部分内容:
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不是更好吗