如何在类库中访问JWT User.Claims

时间:2018-08-28 05:10:26

标签: c# asp.net-core .net-core

我已经使用DDD构建了项目,看起来像这样:

| CompanyName.API
  | MyControllerThatRequiresJwtToken << Entry point
| CompanyName.Application
| CompanyName.Data
  | EfCoreContext << I would like to get the claims here
| CompanyName.Domain
| CompanyName.IoC
| CompanyName.Test
| CompanyName.UI

我正在使用 Z.EntityFramework.Plus.Audit.EFCore 审核所有数据更改。我将其添加到CompanyName.Data项目中,因为这是我的EF上下文所在的位置。

问题是:API中的所有请求都需要JWT令牌。我想在Audit对象中设置发送请求的人的用户名,该用户名将保存到数据库中,但是我无法访问我的数据层中的HttpContext。

获取此信息的最佳方法是什么?也许将IHttpContextAccessor注入数据层?使数据层“依赖于HTTP”听起来不是一个好计划。

更新

我不确定如何将其从Controller传递到上下文。我相信它需要以某种方式注入。

EfCoreContext.cs

的片段
public override int SaveChanges()
{
   var audit = new Audit();
   audit.CreatedBy = "JWT Username"; // << I'd need it here

   audit.PreSaveChanges(this);
   var rowAffecteds = base.SaveChanges();
   audit.PostSaveChanges();

   if (audit.Configuration.AutoSavePreAction != null)
   {
      audit.Configuration.AutoSavePreAction(this, audit);
      base.SaveChanges();
   }

   return rowAffecteds;
}

4 个答案:

答案 0 :(得分:1)

我遇到了这种情况。我的解决方法是这样的:

1-在控制器上获取用户信息,并将该信息提供给您的dto(请求)对象。

我编写了用于获取用户ID的扩展程序:

public static string GetUserId(this HttpContext httpContext)
{
    return httpContext?.User?.Claims?.FirstOrDefault(claim => claim.Type == ClaimTypes.NameIdentifier)?.Value ?? string.Empty;
}

请求对象:

public class CreateMenuRequest
{
    public string MenuName { get; set; }

    [JsonIgnore]
    public string UpdatedBy { get; set; }
}

2-将用户信息设置为请求对象

控制器:

[HttpPost, Route("")]
public IActionResult CreateMenu([FromBody] CreateMenuRequest createMenuRequest)
{
    if (createMenuRequest != null)
    {
        createMenuRequest.UpdatedBy = HttpContext.GetUserId();
    }

    CreateMenuResponse createMenuResponse = _menuService.CreateMenu(createMenuRequest);
    return StatusCode(HttpStatusCode.Created.ToInt(), createMenuResponse);
}

3-在服务层中,经过验证和其他业务需求后,我将请求映射到实体对象。这样的实体对象:

public class Menu : IAudit, ISoftDeletable
{
    public long Id { get; set; }
    ..........

    public string UpdatedBy { get; set; }
    public DateTime UpdateDate { get; set; }
    public string CreatedBy { get; set; }
    public DateTime CreateDate { get; set; }

    public bool IsDeleted { get; set; }
}

4-覆盖SaveChanges以编辑UpdateDate和CreatedDate(如果添加了项,则通过在CreatedBy字段中设置的信息进行更新)。

public override int SaveChanges()
{
   ChangeTracker.DetectChanges();

   IEnumerable<EntityEntry> deletedEntities = ChangeTracker.Entries()
                                                           .Where(t => t.State == EntityState.Deleted && t.Entity is ISof

   foreach (EntityEntry deletedEntity in deletedEntities)
   {
       if (!(deletedEntity.Entity is ISoftDeletable item)) continue;
       item.IsDeleted = true;
       deletedEntity.State = EntityState.Modified;
   }

   IEnumerable<object> addedEntities = ChangeTracker.Entries()
                                                    .Where(t => t.State == EntityState.Added && t.Entity is IAudit)
                                                    .Select(t => t.Entity);
   IEnumerable<object> modifiedEntities = ChangeTracker.Entries()
                                                       .Where(t => t.State == EntityState.Modified && t.Entity is IAudit)
                                                       .Select(t => t.Entity);

   DateTime now = DateTime.UtcNow;

   Parallel.ForEach(addedEntities, o =>
                                   {
                                       if (!(o is IAudit item))
                                           return;
                                       item.CreateDate = now;
                                       item.UpdateDate = now;
                                       item.CreatedBy = item.UpdatedBy;
                                   });

   Parallel.ForEach(modifiedEntities, o =>
                                      {
                                          if (!(o is IAudit item))
                                              return;
                                          item.UpdateDate = now;
                                      });

   return base.SaveChanges();
}

答案 1 :(得分:0)

代替传递IHttpContextAccessor,在Controller中提取用户名,然后将其传递给需要它的方法。 我已经创建了这样的扩展方法,

public static class UserResolverService
{
    public static Guid GetUserId(this ControllerBase controller)
    {
        var result = controller.User.FindFirstValue(ClaimTypes.NameIdentifier);
        return Guid.Parse(result);
    }
}

然后在您的服务方法调用中

 entity= await DomainServices.CreateSomething(this.GetUserId(), dtoObject);

这样,您的服务就不直接依赖于HttpContext,因为它暴露了比所需更多的东西。但是,在任何地方传递用户名都会产生开销。

另一个选择是创建一个依赖于IHttpContext访问器的服务,并公开一个GetUserMethod。然后让其他服务依赖此服务来获取当前用户。这样,只有一个业务层服务将与IHttpContextAccessor耦合。最佳候选人将是您的UserProfile服务。

答案 2 :(得分:0)

创建所需数据的抽象,然后将其作为具有DI的服务注入DbContext中。

// interface
public interface IAppPrincipal
{
    string Name { get; }
}

// concrete class
public class AppPrincipal : IAppPrincipal
{
    public AppPrincipal(string name)
    {
        Name = name;
    }

    public string Name { get; }
}

// db context
public class AppDbContext : DbContext
{
    public AppDbContext(DbContextOptions<AppDbContext> options, IAppPrincipal principal = null)
    {
        Principal = principal;
    }

    public IAppPrincipal Principal { get; }

    public override int SaveChanges()
    {
        var audit = new Audit();
        audit.CreatedBy = Principal?.Name;

        ...
    }
}

// service registration in API or MVC app
services.AddScoped<IAppPrincipal>(provider => 
{
    var user = provider.GetService<IHttpContextAccessor>()?.HttpContext?.User;

    return new AppPrincipal(user?.Identity?.Name);
});

答案 3 :(得分:0)

例如创建一个名为IApplicationUser的接口。为它提供所需的只读属性,例如ID,名称和其他内容。

创建一个实现

public class ApplicationUser : IApplicationUser
{
   private readonly IHttpContextAccessor httpContextAccessor;

   public ApplicationUser(IHttpContextAccessor httpContextAccessor)
   {
      this.httpConntextAccessor = httpContextAccessor;
   }

   public Guid Id => this.GetUserId();

   public string Name => this.GetUserName();

   private Guid GetUserId()
   {
       var subject = this.httpContextAccessor.HttpContext
                         .User.Identity.Claims
                         .FirstOrDefault(claim => claim.Type == JwtClaimTypes.Subject);

       return Guid.TryParse(subject, out var id) ? id : Guid.Empty;
   }

   private Guid GetUserId()
   {
       return this.httpContextAccessor.HttpContext
                         .User.Identity.Claims
                         .FirstOrDefault(claim => claim.Type == JwtClaimTypes.PreferredUserName);
   }
}

现在向您的DI容器注册。对于默认的MS IOC:

services.AddScoped<IApplicationUser, ApplicationUser>();

在需要的地方注入IApplicationUser并使用它来获取用户信息。

编辑IHttpContextAccessor必须已注册。如果不是这种情况,也要这样做

services.AddScoped<IHttpContextAccessor, HttpContextAccessor>();

编辑2:只是为了澄清。这不是要在存储库中使用,或者您想调用它。重新考虑您的逻辑,以便您可以在保存数据之前将该信息传递给您的实体。