Как получить доступ к JWT User.Claims в библиотеке классов - PullRequest
0 голосов
/ 28 августа 2018

Я структурировал свой проект с использованием 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 Context.

Проблема: для всех запросов в API требуется токен JWT. Я хотел бы указать имя пользователя, отправляющего запрос, в объекте Audit, который будет сохранен в базе данных, однако у меня нет доступа к HttpContext в моем слое данных.

Как лучше всего получить эту информацию? Возможно, добавление IHttpContextAccessor в слой данных? Не очень хороший план сделать слой данных "зависимым от Http".

UPDATE

Я не уверен, как бы передать его из контроллера в контекст. Я полагаю, что это нужно как-то ввести.

Фрагмент 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 голосов
/ 28 августа 2018

Я столкнулся с чем-то вроде этой ситуации. Мое решение выглядит следующим образом:

1 - Получить информацию о пользователе на контроллере и передать эту информацию вашему объекту dto (request).

Я пишу расширения для получения идентификатора пользователя:

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();
}
0 голосов
/ 28 августа 2018

Создайте абстракцию необходимых вам данных, а затем внедрите их в DbContext в качестве службы с DI.

// 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);
});
0 голосов
/ 28 августа 2018

Например, создайте интерфейс с именем IApplicationUser. Дайте ему только для чтения свойства, которые вам нужны, такие как идентификатор, имя и что нет.

Создать его реализацию

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: Просто чтобы уточнить. Это не предназначено для использования в хранилище или как бы вы хотели его назвать. Пересмотрите свою логику, чтобы вы могли передать эту информацию своей сущности перед сохранением данных.

0 голосов
/ 28 августа 2018

Вместо передачи 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.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...