Как настроить WASM IdentityServer с аутентификацией пользователя / прохода И аутентификацией устройства с клиентом / секретом из консольного приложения

У меня есть типичный проект Blazor WASM, сервер, клиент и общий доступ. Аутентификация с IdentityServer настроена и работает правильно. Я получаю JWT, когда вхожу в систему с пользователем и могу получить документ обнаружения.

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

Я добавил консольное приложение, работающее на устройстве. Но аутентификация не возвращает invalid_client.

В серверном приложении я определил Client и ApiScope:

public static IEnumerable<ApiScope> ApiScopes =>
    new List<ApiScope>
        {
            new ApiScope("api", "My API")
        };

public static IEnumerable<IdentityServer4.Models.Client> Clients =>
    new List<IdentityServer4.Models.Client>
    {
        new IdentityServer4.Models.Client
        {
            ClientId = "device",

            // no interactive user, use the clientid/secret for authentication
            AllowedGrantTypes = GrantTypes.ClientCredentials,

            // secret for authentication
            ClientSecrets =
            {
                new Secret("secret")
            },

            // scopes that client has access to
            AllowedScopes = { "api" }
        }
    };

Они добавляются с помощью функций AddInMemory в методе Startup.Configure:

        services.AddIdentityServer()
            .AddInMemoryApiScopes(Config.ApiScopes)
            .AddInMemoryClients(Config.Clients)
            .AddApiAuthorization<ApplicationUser, ApplicationDbContext>(options =>
            {
                options.IdentityResources["openid"].UserClaims.Add("name");
                options.ApiResources.Single().UserClaims.Add("name");
                options.IdentityResources["openid"].UserClaims.Add("role");
                options.ApiResources.Single().UserClaims.Add("role");
            });

        JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("role");

        services.AddAuthentication("Bearer")
            .AddJwtBearer("Bearer", options =>
            {
                options.Authority = "https://localhost:44316";
                options.Audience = "api";
            }) 
            .AddIdentityServerJwt();

В консольном приложении я отправляю DeviceAuthenticationRequest:

static IDiscoveryCache _cache = new DiscoveryCache("https://localhost:44316");

var disco = await _cache.GetAsync();
if (disco.IsError) throw new Exception(disco.Error);

var client = new HttpClient();
response = await client.RequestDeviceAuthorizationAsync(new DeviceAuthorizationRequest
{
    //Address = disco.TokenEndPoint, // same result
    Address = disco.DeviceAuthorizationEndpoint,
    ClientId = "device",
    ClientSecret = "secret",
    Scope = "api"
});

Ответ в клиенте содержит ошибку invalid_client. И сервер тоже жалуется:

IdentityServer4.Validation.ClientSecretValidator: Error: No client with id 'client' found. aborting

Надеюсь, мой вопрос ясен, заранее спасибо.

РЕДАКТИРОВАТЬ Очевидно, что ошибка была опечаткой. Но мне удалось это исправить, изменив AllowedGrantTypes на:

AllowedGrantTypes = { GrantTypes.ClientCredentials, GrantTypes.DeviceFlow };  

Тем не мение! Мне пришлось удалить авторизацию api, нужно выяснить, как добавить ее обратно:

.AddApiAuthorization<ApplicationUser, ApplicationDbContext>(options =>
{
    options.IdentityResources["openid"].UserClaims.Add("name");
    options.ApiResources.Single().UserClaims.Add("name");
    options.IdentityResources["openid"].UserClaims.Add("role");
    options.ApiResources.Single().UserClaims.Add("role");
});

И вместо указания параметров и добавления JwtBearer:

//services.AddAuthentication("Bearer")
//    .AddJwtBearer("Bearer", options =>
//    {
//        options.Authority = "https://localhost:44316";
//        options.Audience = "api";
//    }) 
//    .AddIdentityServerJwt();

У меня только вот это:

services.AddAuthentication()
    .AddIdentityServerJwt();

person fonZ    schedule 14.11.2020    source источник


Ответы (1)


Думаю, у вас небольшая опечатка. В Startup.cs значение ClientId = "device". В вашем клиенте вы устанавливаете ClientId = "client".

В общем, ты прав. Вам не нужно создавать клиента для каждого пользователя и его потенциальных устройств. Достаточно определения одного клиента для всех возможных пользователей и устройств. Вы даже можете поспорить, нужен ли пароль клиента для повышения безопасности. В официальных документах секрет не является обязательным.

В упрощенном виде: клиентская часть этого потока аутентификации в основном используется для генерации пользовательского кода. На втором (асинхронном) шаге пользователь с разными логинами устройства аутентифицирует устройство, вводя ранее сгенерированный код.

Дополнительные сведения см. В этом сообщении. .

person Just the benno    schedule 14.11.2020
comment
Спасибо за опечатку, но результат тот же. Однако это не устройство пользователя, это просто устройство. Я не хочу вовлекать пользователя. У меня есть отдельные пользователи, но они не владеют устройством. Устройство стоит самостоятельно. - person fonZ; 14.11.2020
comment
Несмотря на ошибку аутентификации клиента, давайте сосредоточимся на том, чего вы хотите достичь в процессе аутентификации. Потоку устройств всегда нужен пользователь. Устройство будет обращаться к вашему API от имени пользователя. Если я правильно помню, приложение Amazon Prime Video на Xbox имеет такое поведение. DeviceController быстрого запуска IdentityServer4 использует атрибут Authorize. Если вы хотите вызвать свой API без пользователя, это означает, что у вас есть поток от сервера к серверу. В этом случае используйте клиент и секрет. - person Just the benno; 14.11.2020
comment
Чтобы разместить API и IdentityServer в одном проекте, посмотрите этот пример github.com/brockallen/IdentityServerAndApi - person Just the benno; 14.11.2020
comment
спасибо @ just-the-benno, это помогло мне прояснить путаницу. Я был уверен, что смогу сделать это без пользователя. Изменил на clientcredentials, теперь он работает, я получаю токен. Но затем я хочу использовать токен для установки соединения SignalR с помощью AccessTokenProvider в методе WithUrl, и это говорит мне, что он не авторизован. Мне нужно выяснить, как [Авторизовать] SignalR comm с токеном. - person fonZ; 16.11.2020
comment
Действительно, аутентификация SignalR и jwt особенные. В отличие от обычных HTTP-вызовов, SignalR и протокол WebSocket не используют концепцию заголовков. Обходной путь - отправить токен в качестве параметра запроса. Это должен сделать клиент. На стороне сервера, чтобы иметь такой же уровень комфорта, мы перехватываем процесс аутентификации и устанавливаем токен из запроса в качестве токена доступа. Этот документ [docs.microsoft.com/en- us / aspnet / core / signalr / authn-and-authz] объясняет, как это делается. Проверьте, отправляет ли ваш клиент токен в виде запроса и получает ли его сервер. - person Just the benno; 16.11.2020
comment
Я читаю этот документ уже неделю. Я получаю токен сейчас, я также передаю его как строку запроса в HubConnectionBuilder, но как только я использую [Authorize (Policy = ApiScope)] или просто [Authorize] на концентраторе, я получаю 401. services.AddAuthorization (options = ›{Options.AddPolicy (ApiScope, policy =› {//policy.RequireAuthenticatedUser (); policy.RequireClaim (scope, api);});}); - person fonZ; 17.11.2020
comment
Вы проверили, что токен отправляется как параметр запроса? Проверьте связь через вкладку сети в консоли разработчика вашего браузера. Установите точку останова в методе OnMessageReceived, чтобы увидеть, что произойдет. Не установлен ли context.Token? Если серверный метод не вызывается, это означает, что авторизация настроена неправильно. Если он запущен и работает правильно, но вы по-прежнему получаете 401. Попробуйте сначала вызвать обычный API-вызов, чтобы увидеть, работает ли аутентификация в целом, и продолжайте свой путь оттуда. - person Just the benno; 17.11.2020
comment
После успешного получения токена на клиенте и попытки подключиться к контексту хаба, токен правильно настроен в OnMessageReceived. Это происходит только тогда, когда путь - myhub /gotiate. Если продвинуться дальше, сразу после завершения OnMessageReceived отправляется 401 и процесс завершается. Я работаю с консольным приложением и HttpClient. Аутентификация работает, у меня есть клиент Blazor wasm, который также принимает / отправляет и аутентифицирует связь SignalR. Однако не с секретными учетными данными клиента, а с паролем пользователя, и мне пришлось добавить код AddApiAuthorization, чтобы добавить поддержку ролей. - person fonZ; 17.11.2020
comment
Не могли бы вы прислать мне репо, чтобы с ним поиграться? - person Just the benno; 17.11.2020
comment
Если бы я мог, проект слишком сложен, чтобы его можно было передать, и он проприетарный. Сейчас я настраиваю его с нуля с помощью шаблонов .net 5, чтобы проверить, не столкнусь ли я с теми же проблемами. Возможно, я что-то упустил при переходе с 3.1 на 5.0 ... или сделанные мной настройки создают проблемы. Надеюсь разобраться: / - person fonZ; 18.11.2020
comment
может ты мог бы проверить мой другой вопрос? Я сузил круг вопросов. stackoverflow.com/questions/64955291/ - person fonZ; 22.11.2020
comment
Привет. Спасибо. Я прочитал его и у меня есть несколько идей, которые я хочу сначала протестировать. Сегодня немного занято, но постараюсь ответить позже. :) - person Just the benno; 23.11.2020
comment
Спасибо за помощь, не беспокойтесь. У меня он работает для пользователя / пароля и учетных данных клиента. Сейчас работаем над добавлением SignalR. Мне пришлось добавить клиента в IClientStore, чтобы клиент Blazor работал. Обычно он добавляется AddIdentityJwt, но я думаю, он перезаписывается или что-то в этом роде, не уверен. - person fonZ; 25.11.2020