Некоторое время назад перед нами возникла задача разграничения прав доступа к ресурсам, то есть задача аутентификации и управления аутентификацией. Поскольку архитектура основных проектов представляла нечто похожее на распределенный монолит, мы решили остановиться на Identity Server.
-
Identity Server
-
Особенности конфигурации
-
Применение
Identity Server
Наверное лучший способ понять, что такое Identity Server — это прочитать документацию. Если вкратце, то могу сказать, что IS представляет собой OpenID Connect и OAuth 2.0 фреймворк для ASP.NET Core. Стоит отметить, что потенциал фреймворка достаточно огромный.
Если говорить о способах его применения, то, в целом, можно отметить два основных:
-
внутри приложения — это способ встраивание фреймворка в основное приложение или ядро системы. Такой способ хорош скоростью работы, а также возможностью комбинирования логики и аутентификации, однако не очень хорош с точки зрения гибкости и приводит к нагромождению кода;
-
отдельный сервис — это выделение фреймворка в отдельный рабочий процесс, такого как — сервис аутентификации, запросы к которому будут идти по сети. К преимуществам здесь можно отнести гибкость и разделение кода, но расплачиваться приходится скоростью;
Ресурсами, которые защищаются с помощью Identity Server, могут быть хранилища файлов, различные сервисы предоставляющие данные, адаптеры конфигурации и прочее. Чтобы ограничить доступ к ресурсам необходимо сконфигурировать IS. Сама конфигурация может храниться в различных местах, например, конфигурация может храниться в базе данных, взаимодействие с которой может осуществляться через EF Core.
var migrationsAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name; const string connectionString = … services.AddIdentityServer() .AddConfigurationStore(options => { options.ConfigureDbContext = b => b.UseSqlServer(connectionString, sql => sql.MigrationsAssembly(migrationsAssembly)); }) .AddOperationalStore(options => { options.ConfigureDbContext = b => b.UseSqlServer(connectionString, sql => sql.MigrationsAssembly(migrationsAssembly)); });
Здесь подключается IS через AddIdentityServer(), а методы AddConfigurationStore и AddOperationalStore инициализируют конфигурацию и загружают оперативные данные из БД. Если использовать базу данных, то можно «на лету» менять конфигурацию Identity Server и при этом не нужно будет обновлять сервис.
Так же конфигурацию можно хранить в файле, в таком случае, при запуске приложения, она будет загружена в оперативную память.
services.AddIdentityServer() .AddInMemoryClients(Clients) .AddInMemoryIdentityResources(Resources) .AddInMemoryApiResources(ApiResources) .AddInMemoryApiScopes(Scopes);
Здесь загружаются Clients, Resources, Scopes, ApiResources, ApiScopes в оперативную память, о них пойдет речь ниже.
Особенности конфигурации
Из прошлых вставок кода можно было заметить конфигурационный набор в виде Clients, Resources, ApiResources, Scopes. Наверное, это основные конфигурации, которые нужно встраивать в проект, но что же они означают? Давайте разбираться
Клиенты
Clients —это клиенты, которые могут подключиться к IS и получить разрешение на использование некоторых ресурсов или областей.
{ "ClientId": "client_id", "ClientSecrets": [ { "Value": "xxx" } ], "AccessTokenLifetime": "86400", "AllowedGrantTypes": [ "client_credentials" ], "AllowedScopes": [ "openid", "profile", ] }
Как и каждый пользователь в современном приложении, в конфигурации клиента есть логин и пароль. В качестве логина здесь выступает “ClientId”, а в качестве пароля служит “ClientSecrets” в зашифрованном виде.
Нужно понимать, что клиентами, как правило, являются приложения — это приводит нас к необходимости иметь различные типы аутентификации клиентов, за это отвечает параметр “AllowedGrantTypes” в конфигурации клиента. Рассмотрим основные 5 не гибридных типов, каждый из них предназначен для своего сценария входа.
-
client credentials — предназначен для коммуникации машины к машине — токен запрашивается непосредственно от имени клиента;
-
authorization code — предназначен для работы с интерактивными пользователями клиентского приложения;
-
device flow — предназначен для работы с устройствами без браузера или с ограниченными возможностями ввода, к таким относятся например Apple TV;
-
implicit —в настоящее время всё больше теряет актуальность, так он был предназначен для собственных приложений и приложений JS, где токен доступа возвращался немедленно без дополнительного шага обмена кода авторизации;
-
resource Owner Password — используется в случае доверительных отношений с клиентом, например для приложений с высоким уровнем привилегий;
Полученный доступ был бы бесконечным, если бы в недрах Identity Server не было бы ограничений по времени, которое настраивается полем “AccessTokenLifetime”. AccessTokenLifetime – это время жизни токена доступа. Помимо токена доступа в некоторых типах авторизации используется “RefreshToken”. RefreshToken – токен обновления, который позволяет продлевать доступ, для этого необходимо проставить параметр “AllowOfflineAccess” в true и выполнить запрос вида:
POST /connect/token client_id=client& client_secret=secret& grant_type=refresh_token& refresh_token=hdh922
Ресурсы
Ресурсы в Identity Server 4 разделяются на два вида:
-
Identity Resources — это ресурсы пользователя такие как: идентификатор пользователя, логин, e-mail и так далее;
-
API Resources — это функциональные ресурсы, к которым может получить доступ клиент, сюда могут относиться как методы апи, так и очереди сообщений, и прочий функционал;
Так, например, ресурсом может быть хранилище файлов или, например, пространствами (scopes служит для разделения ограничений, но об этом позже)директории внутри хранилища. Также, частый случай, когда ресурсом является целый сервис, а пространствами функциональные части сервиса.
Пространства
Наверное самый простой способ понять, что такое пространства это представить себе минимальный ресурс, например, сервис, который работает с пользователями и реализует операции CRUD (create, read, update, delete) — эти виды операций с сервисом и есть пространства, однако стоит отметить, что существуют и другие варианты адаптации ресурсов и пространств к задаче. Например, пространства могут уточнять определенные ресурсы, к которым запрашивается доступ. В частности, мы можем запросить доступ не ко всем пользователям, а к определенной группе, которая будет указана в пространстве.
Профиль сервис
Профиль сервис предназначен для расширения возможностей по доступу к идентификационным данным пользователей. С его помощью можно идентифицировать, валидировать пользователей, а также влиять на доступ, ограничивать его при необходимости, добавлять свои claims в токен и многое другое (документация). Стоит отметить, что пользователями могут быть как реальные пользователи системы, так и просто части или модули приложения. Сервис должен реализовать интерфейс IProfileService и добавить в конвейер:
services.AddIdentityServer() .AddProfileService<ProfileService>();
Сервис валидации
Помимо ProfileService можно реализовать свой сервис валидации, который будет проверять как токен доступа, так и весь запрос. Валидация производится после выполнения запроса через основную логику аутентификации и перед отправкой ответа клиенту. С её помощью, можно менять токен доступа, влиять на сведения в этом токене или ограничивать доступ к определенным сущностям (подробнее). Сервис должен реализовать интерфейс ICustomTokenRequestValidator и встроить его в конвейер:
services.AddIdentityServer() .AddCustomTokenRequestValidator<ClientTokenValidatorService>();
Применение
Итак, Identity Server мы сконфигурировали, а также приняли решение держать его отдельно как самостоятельный сервис, который выдает токены доступа на определенные ресурсы при правильных параметрах запроса. Теперь необходимо заставить сервисы или ресурсы проверять эти токены. И здесь также есть два пути:
Первый и самый очевидный — это проверять токен доступа на самом ресурсе или сервисе, этот способ самый гибкий и самый быстрый. На рисунке у каждого ресурса есть слой, который проверяет токен и управляет доступом к ресурсу. В такой реализации можно применять индивидуальные настройки для каждого ресурса. Например, валидация токена для файлового хранилища может быть сконфигурирована так:
var tokenValidationParameters = new TokenValidationParameters { ValidateAudience = false, ValidateIssuerSigningKey = true, ValidateLifetime = false, IssuerSigningKey = securityKey, ValidateIssuer = false, ClockSkew = TimeSpan.FromMinutes(5) }; services.AddAuthentication("Bearer") .AddJwtBearer("Bearer", options => { options.TokenValidationParameters = tokenValidationParameters; });
здесь используется схема аутентификации Bearer, а валидируется только цифровая подпись и учитывается поправка на время функционирования токена (актуально если ресурс и IS на разных машинах, где отличается время). В свою очередь некоторый открытый сервис работы с юзерами может валидировать токен по собственными внутренним правилам.
Обратной стороной здесь является тот факт, что каждый сервис должен уметь читать токен, а значит и иметь ключи доступа на чтение и в случае, когда их нужно будет сменить, придется переопубликовать все ресурсы, зависящие от ключей.
Здесь появляется отдельный сервис, проверки токена и управления аутентификацией. Есть также варианты, когда сервис проверки токена встроен с Identity Server.
Конфигурация валидации и чтения токена доступа будет храниться в одном месте. Этот подход может стать лучшей альтернативой, поскольку он решает все минусы предыдущего, но здесь появляется взаимодействие по сети, балансировка нагрузки и прочие особенности микросервисного подхода, что в целом добавляет время проверки токена и, к слову, каждый ресурс должен знать о том к кому обращаться, что приводит нас к единому хранилищу конфигураций, впрочем это уже другая история.
ссылка на оригинал статьи https://habr.com/ru/articles/827572/
Добавить комментарий