Как получить текущего пользователя в .NET Core Web API (от JWT Token)

После множества проблем (и много тутуриалов, руководств и т.д.) мне удалось настроить небольшой .NET API REST Web-сайта с Auth Controller, выдающим токены JWT, когда сохранены имя пользователя и пароль.

Токен хранит идентификатор пользователя как подпункт.

Мне также удалось настроить веб-API для проверки этих токенов, когда метод использует аннотацию Authorize.

 app.UseJwtBearerAuthentication(...)

Теперь мой вопрос: Как я могу прочитать идентификатор пользователя (сохраненный в заявке) в моих контроллерах (в веб-API)?

В основном этот вопрос (Как получить текущего пользователя в ASP.NET Core), но мне нужен ответ для веб-api. И у меня нет UserManager. Поэтому мне нужно где-то прочитать предметный иск.

Ответ 1

Вы можете использовать этот метод:

var email = User.FindFirst("sub")?.Value;

В моем случае я использую электронное письмо как уникальное значение

Ответ 2

Принятый ответ не работал для меня. Я не уверен, было ли это вызвано тем, что я использовал .NET Core 2.0 или чем-то еще, но похоже, что фреймворк сопоставляет требование субъекта с утверждением NameIdentifier. Итак, у меня сработало следующее:

string userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;

Обратите внимание, что это предполагает, что Subject sub Claim установлен в JWT, а его значением является идентификатор пользователя.

По умолчанию обработчик аутентификации JWT в .NET сопоставляет System.Security.Claims.ClaimTypes.NameIdentifier заявку токена доступа JWT с типом заявки System.Security.Claims.ClaimTypes.NameIdentifier. [Источник]

На GitHub также есть дискуссионная ветка, в которой они приходят к выводу, что такое поведение сбивает с толку.

Ответ 3

Если вы используете Name для хранения ID здесь:

var tokenDescriptor = new SecurityTokenDescriptor
{
    Subject = new ClaimsIdentity(new Claim[]
                {
                    new Claim(ClaimTypes.Name, user.Id.ToString())
                }),
    Expires = DateTime.UtcNow.AddDays(7),
    SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};

В каждом методе контроллера вы можете скачать идентификатор текущего пользователя:

var claimsIdentity = this.User.Identity as ClaimsIdentity;
var userId = claimsIdentity.FindFirst(ClaimTypes.Name)?.Value;

Ответ 4

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

Как отметил в комментариях Маркус Хеглунд:

Это должно быть то же самое для "web api". В ASP.NET Core Mvc и Web Api объединены для использования одного контроллера.

Это определенно верно и абсолютно правильно.


Потому что это все одинаково для .NET и .NET Core.

Назад, чем я был новичком в .NET Core и на самом деле весь мир .NET. Важной недостающей информацией было то, что в .NET и .NET Core вся аутентификация может быть обрезана до пространства имен System.Security.Claims с его ClaimsIdentity, ClaimsPrinciple и Claims.Properties. И поэтому он используется в обоих типах контроллеров .NET Core (API и MVC или Razor или...) и доступен через HttpContext.User.

Важное примечание: все учебники пропущены.

Поэтому, если вы начнете что-то делать с токенами JWT в .NET, не забудьте также убедиться в надежности ClaimsIdentity, ClaimsPrinciple и Claim.Properties. Это все об этом. Теперь ты это знаешь. Это было указано Герингером в одном из комментариев.


ВСЕ промежуточные программы аутентификации на основе утверждений (если они правильно реализованы) заполняют HttpContext.User утверждениями, полученными во время проверки подлинности.

Насколько я понимаю, теперь это означает, что можно безопасно доверять значениям в HttpContext.User. Но подождите немного, чтобы понять, что нужно учитывать при выборе промежуточного программного обеспечения. Уже доступно много различных промежуточных программ аутентификации (в дополнение к .UseJwtAuthentication()).

С небольшими настраиваемыми методами расширения вы можете теперь получить текущий идентификатор пользователя (точнее утверждение субъекта)

 public static string SubjectId(this ClaimsPrincipal user) { return user?.Claims?.FirstOrDefault(c => c.Type.Equals("sub", StringComparison.OrdinalIgnoreCase))?.Value; }

Или вы используете версию в ответ Ateik.


НО ЖДУ: есть одна странная вещь

Следующее, что меня смутило, это то, что: в соответствии со спецификацией OpenID Connect я искал "суб" заявку (текущий пользователь), но не смог ее найти. Как Хонза Кальфус не мог сделать в своем ответе.

Зачем?

Потому что Microsoft "иногда" "немного" отличается. Или, по крайней мере, они делают немного больше (и неожиданных) вещей. Например, официальное промежуточное программное обеспечение аутентификации Microsoft JWT Bearer, упомянутое в первоначальном вопросе. Microsoft решила преобразовать претензии (названия претензий) во все их официальное промежуточное ПО аутентификации (по причинам совместимости я не знаю более подробно).

Вы не найдете "подчиненную" заявку (хотя это единственная заявка, указанная OpenID Connect). Потому что он превратился в эти модные ClaimTypes. Это не все плохо, это позволяет вам добавлять сопоставления, если вам нужно сопоставить разные заявки с уникальным внутренним именем.

Либо вы придерживаетесь именования Microsoft (и должны помнить об этом, когда добавляете/используете промежуточное программное обеспечение не от Microsoft), либо вы узнаете, как изменить соответствие утверждений для промежуточного программного обеспечения Microsoft.

В случае JwtBearerAuthentication это сделано (сделайте это рано в StartUp или по крайней мере перед добавлением промежуточного программного обеспечения):

JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();

Если вы хотите придерживаться именования Microsoft предметной претензии (не бейте меня, я не уверен прямо сейчас, если имя является правильным сопоставлением):

    public static string SubjectId(this ClaimsPrincipal user) { return user?.Claims?.FirstOrDefault(c => c.Type.Equals(ClaimTypes.NameIdentifier, StringComparison.OrdinalIgnoreCase))?.Value; }

Обратите внимание, что другие ответы используют более продвинутый и более удобный метод FindFirst. Хотя мои примеры кода показывают это без них, вы можете пойти с ними.

Таким образом, все ваши претензии хранятся и доступны (через одно или другое имя) в HttpContext.User.


Но где мой токен?

Я не знаю для другого промежуточного программного обеспечения, но Аутентификация на носителе JWT позволяет сохранять токен для каждого запроса. Но это нужно активировать (в StartUp.ConfigureServices(...).

services
  .AddAuthentication("Bearer")
  .AddJwtBearer("Bearer", options => options.SaveToken = true);

Фактический токен (во всей его загадочной форме) в виде строки (или ноль) может быть доступен через

HttpContext.GetTokenAsync("Bearer", "access_token")

Была более старая версия этого метода (это работает для меня в .NET Core 2.2 без устаревшего предупреждения).

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


Ну, я надеюсь, что это резюме поможет вам.

Ответ 5

Вы можете сделать это, используя.

User.Identity.Name

Ответ 6

Я использовал HttpContext, и он хорошо работает:

var email = string.Empty;
if (HttpContext.User.Identity is ClaimsIdentity identity)
{
    email = identity.FindFirst(ClaimTypes.Name).Value;
}