Шаблон для получения информации пользователя во всех моих сервисах из токена JWT

#asp.net-core #.net-core #design-patterns #asp.net-core-middleware

#asp.net-ядро #.net-core #дизайн-шаблоны #asp.net-core-промежуточное программное обеспечение

Вопрос:

У меня есть API со многими контроллерами. Один из этих контроллеров является контроллером аутентификации. Он используется для получения токена JWT и вызова других путей с этого контроллера или другого.

Моя проблема в том, что для любых путей мне нужно получить идентификатор пользователя в токене JWT и запросить у базы данных информацию о пользователе, например, проверить, существует ли пользователь или данные, связанные с ним. Поэтому в каждом методе я должен вызывать определенный метод для получения информации.

Фактически, уровень аутентификации используется только для проверки того, действителен ли токен JWT. Я не знаю, возможно ли добавить некоторую логику в этот уровень. И как.

Могу ли я реализовать какой-либо шаблон для автоматического извлечения информации пользователя?

Я думал о singleton, но я не уверен в области видимости объекта. Цель состоит в том, чтобы оставаться в области запроса.

Я думал создать пользовательский сервис, но я думаю, что это не очень хороший способ, потому что сервисы предназначены для обработки, а не для хранения данных в них.

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

Цель состоит в том, чтобы реализовать всю пользовательскую логику в одном и том же месте, и каждая служба может вызывать ее, чтобы справиться с ней :

 Request (with JWT Token) --> Controller --> Service --> Call the object with all user's stuff and logic and treatment
or
Request (with JWT token) --> middleware (get all user's informations) --> Controller --> Call the object created in middleware with all user's stuff
 

Для получения дополнительной информации, есть мой метод входа в систему и способ создания токена JWT :

 public async Task<ServiceResponse<UserLoginResponseDto>> Login(ServiceRequest<UserLoginRequestDto> request)
{
    ServiceResponse<UserLoginResponseDto> response = new ServiceResponse<UserLoginResponseDto>();
    User user = await _context.Users.FirstOrDefaultAsync(x => x.Username.ToLower().Equals(request.Data.Username.ToLower()));
    if (user == null)
    {
        response.Success = false;
        response.Message = "User not found";
    }
    else if (user.IsLocked)
    {
        response.Success = false;
        response.Message = "User locked";
    }
    else if (!user.IsActivated)
    {
        response.Success = false;
        response.Message = "User not activated";
    }
    else if (!VerifyPasswordHash(request.Data.Password, user.PasswordHash, user.PasswordSalt))
    {
        response.Success = false;
        response.Message = "Wrong password";
    }

    else
    {
        response.Data = _mapper.Map<UserLoginResponseDto>(user);
        response.Data.JWtToken = CreateToken(user);
    }

    return response;
}

private string CreateToken(User user)
{
    List<Claim> claims = new List<Claim>
    {
        new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
        new Claim(ClaimTypes.Name, user.Username),
    };

    SymmetricSecurityKey key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config.GetSection("AppSettings:Token").Value));

    SigningCredentials creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha512Signature);

    SecurityTokenDescriptor tokenDescriptor = new SecurityTokenDescriptor
    {
        Subject = new ClaimsIdentity(claims),
        Expires = DateTime.Now.AddDays(1),
        SigningCredentials = creds
    };

    JwtSecurityTokenHandler tokenHandler = new JwtSecurityTokenHandler();
    SecurityToken token = tokenHandler.CreateToken(tokenDescriptor);

    return tokenHandler.WriteToken(token);
}
 

Комментарии:

1. то, что вы хотите, должно быть функцией выбора, которая будет применяться только к некоторым конкретным конвейерам обработки запросов (начиная с действия контроллера / обработчика страницы). Таким образом, использование промежуточного программного обеспечения — это нормально, но вам нужно реализовать свой собственный фильтр. Я бы использовал фильтр действий / фильтр страниц для этого сценария. Обратите внимание, что извлечение данных для каждого запроса должно быть как можно более быстрым, поэтому, похоже, вам также необходимо реализовать некоторый пользовательский информационный кэш (с правильным способом обновления кэша). Фильтр действий может получить информацию о пользователе и поместить в HttpContext нее информацию, представляющую общий контекст для текущего запроса.

2. Спасибо за ответ. Есть ли у вас какие-либо примеры / учебники / статьи об этом способе?

3. нам не нужен пример для таких простых вещей. Все, что вам нужно знать, это конвейерная модель ASP.NET Ядро, это как поток выполнения кода, в этом потоке у вас есть несколько точек расширения. Мы можем ввести наш код в одну из таких точек, чтобы достичь того, чего мы хотим. В этом случае мы выбираем фильтр действий IActionFilter (или IAsyncActionFilter ). Еще один более ранний момент IAuthorizationFilter — это то, что может быть лучше. В вашем коде есть ввод UserId (из HttpContext.Request ), из которого вы можете загрузить информацию о пользователе и вставить ее HttpContext.Items для следующего использования кода.

4. На самом деле логика, которую вам нужно выполнить, делится на 2 задачи: первая — когда загружать информацию о пользователе в хранилище в памяти. второй — как загрузить эту информацию в хранилище в памяти, а также как использовать ее из любого места в следующем коде в том же конвейере. Что касается второй проблемы, вы можете реализовать ее по-своему, разработав свой собственный механизм для хранения информации в памяти, чтобы ее можно было легко использовать (доступно только в текущем контексте обработки запроса). Наиболее удобным способом (как я уже сказал выше) является использование HttpContext.Items .

5. То, что я прокомментировал, — это принцип, он гораздо более ценен, чем конкретный пример / демонстрация, чтобы вы могли использовать его мгновенно. Для предоставления такого ответа требуется больше ввода, и я не предпочитаю этого делать. Вы можете искать больше или ждать, пока здесь присоединится другой, чтобы помочь с конкретным ответом.