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

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

Маркер хранит идентификатор пользователя в качестве дополнительного утверждения.

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

 app.UseJwtBearerAuthentication(...)

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

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


person monty    schedule 08.09.2017    source источник
comment
Это должно быть одинаково для веб-API. В ASP.NET Core Mvc и веб-API объединены для использования одного и того же контроллера.   -  person Marcus Höglund    schedule 19.12.2017


Ответы (8)


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

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

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

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

Также существует ветка обсуждения на GitHub, где они приходят к выводу, что такое поведение сбивает с толку.

person Honza Kalfus    schedule 27.03.2018
comment
Или немного короче: User.FindFirstValue(ClaimTypes.NameIdentifier) - person Seafish; 10.08.2019
comment
@Seafish Обязательно сделайте нулевую проверку и в этом случае. - person Honza Kalfus; 15.02.2020

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

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

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

person Ateik    schedule 19.12.2017
comment
Спасибо, должен быть отмечен как принятый! Для имени пользователя: User.Identity.Name. Пользователь является свойством Microsoft.AspNetCore.Mvc.ControlerBase и имеет тип System.Security.Claims.ClaimsPrincipal. Просто добавляю. - person heringer; 06.03.2018
comment
другой способ: string sub = HttpContext? .User.Claims.FirstOrDefault (c = ›c.Type == System.Security.Claims.ClaimTypes.NameIdentifier) ​​.Value; - person monty; 12.03.2018
comment
Или User.FindFirstValue (ClaimTypes.Name) - person Gustavo Piucco; 31.01.2019
comment
Обратите внимание: если вы не используете атрибуты [Authorize], этот User может быть своего рода пустым пользователем, где User.Identity.IsAuthenticated - ложь. Так что остерегайтесь этого. - person Simon_Weaver; 04.02.2019

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

Или вы используете версию в ответе Атейка.


НО ПОДОЖДИТЕ: есть одна странная вещь

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

Почему?

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

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

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

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

JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();

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

    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 помогает.


Что ж, надеюсь, это резюме вам тоже поможет.

person monty    schedule 18.04.2019
comment
Очень важно, чтобы утверждения в фактическом токене точно совпадали с тем, что доступно в контроллере. Я думаю, ваш ответ должен начинаться с этого в Startup.cs: JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear (); Затем это в Controller.cs: var userSub = User.FindFirst (sub) ?. Value; Это был единственный ответ, который полностью объяснил проблему без ссылки на запутанную ветку на GitHub. Отличная работа. - person jezpez; 28.04.2019
comment
Хотел бы я прочитать этот ответ раньше. Потратил час, чтобы узнать, как .Net изменил тип заявки. Я явно назвал подпрограмму претензии, и я просто не мог понять, почему работает jti, а не подпрограмма. Это может запутать многих людей, плохо знакомых с JWT в .Net Core. Ваш ответ проясняет это. - person farshad; 06.04.2020

Если вы используете 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;
person ImpoUserC    schedule 11.09.2018
comment
var userId = User.Identity.Name - person troYman; 19.07.2020

вы можете сделать это с помощью.

User.Identity.Name

person Imamul Karim Tonmoy    schedule 09.02.2019

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

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

В моем случае я установил для ClaimTypes.Name уникальный адрес электронной почты пользователя перед генерацией токена JWT:

claims.Add(new Claim(ClaimTypes.Name, user.UserName));

Затем я сохранил уникальный идентификатор пользователя в ClaimTypes.NameIdentifier:

claims.Add(new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()));

Затем в коде контроллера:

int GetLoggedUserId()
        {
            if (!User.Identity.IsAuthenticated)
                throw new AuthenticationException();

            string userId = User.Claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier).Value;

            return int.Parse(userId);
        }
person nikolai.serdiuk    schedule 09.07.2020

Мой работал с использованием следующего кода в веб-API .net core 5

User.Claims.First(x => x.Type == "id").Value;
person Anmol Maini    schedule 14.06.2021