Интеграция Keycloak - Angular: Практическое руководство по подключению приложения к системе управления идентификацией и доступом (IAM)
Keycloak является оптимальным выбором для реализации механизмов аутентификации и авторизации в Angular-приложении. Ниже представлено пошаговое руководство по интеграции Keycloak с Angular.
В современных условиях динамичной разработки, где безопасность играет ключевую роль, интеграция системы управления идентификацией (IAM) с фронтенд-слоем — частью решения, которую видят и с которой взаимодействуют пользователи — становится неотъемлемым элементом ИТ-проектов.
Одним из признанных решений в мире программирования является Keycloak — open-source-система для управления идентификацией и доступом (IAM). Она не только предлагает готовый набор мощных инструментов для работы с аутентификацией и авторизацией, но и позволяет гибко настраивать функционал в соответствии с индивидуальными требованиями проекта.
В этой статье мы покажем, как интегрировать Keycloak с фронтенд-частью приложения на Angular. Мы разберем необходимые шаги для успешного подключения системы к вашему фронтенду и рассмотрим преимущества такой интеграции — в том числе новые возможности, появившиеся в последних версиях Keycloak. Также мы дадим рекомендации по настройке и приведем практические примеры реализации, чтобы помочь разработчикам эффективно защитить свои приложения, сохранив при этом удобный и дружелюбный интерфейс для пользователей.
Что такое Keycloak, каковы его преимущества?
Keycloak — это мощное open-source-решение, предоставляющее функционал для аутентификации, авторизации, федеративной идентификации (federated identity), управления сеансами (session management) и многих других сложных аспектов безопасности. Это делает его идеальным выбором для проектов, требующих защищённой и интегрированной среды.
Keycloak предлагает множество способов быстрой и простой настройки, что позволяет удовлетворить потребности самых разных пользователей. Платформа поддерживает множество стандартных протоколов, включая OpenID Connect, OAuth 2.0 и SAML 2.0, обеспечивая совместимость с различными приложениями. Такой подход позволяет реализовать функционал единого входа (SSO, Single Sign-On) независимо от архитектуры вашего решения.
Keycloak с поддержкой контейнеризации представляет собой идеальное решение для контейнерных сред, таких как Docker и Kubernetes. Это подтверждается следующими возможностями платформы:
- Официальные Docker-образы — команда Keycloak предоставляет и поддерживает официальные образы Docker, гарантируя их актуальность и безопасность.
- Конфигурация через переменные окружения — позволяет настраивать различные параметры (БД, SSL-соединения, конфигурацию realm и др.) без изменения конфигурационных файлов.
- Интеграция с системами оркестрации — Keycloak легко интегрируется с платформами оркестрации, такими как Kubernetes, и может использоваться в средах, управляемых оркестратором. Это обеспечивает динамическое управление экземплярами Keycloak, простое масштабирование в зависимости от нагрузки и использование преимуществ контейнерной оркестрации.
- Подробная документация — Keycloak также предоставляет исчерпывающую документацию по настройке и развертыванию в контейнерах, что упрощает понимание возможностей интеграции с такими инструментами, как Podman, Docker, Kubernetes и OpenShift.
Благодаря этому Keycloak стал одним из самых популярных решений для управления идентификацией и доступом (IAM).
Конфигурирование проекта.
Чтобы продемонстрировать преимущества использования Keycloak, мы развернём сервер и интегрируем его с простым фронтенд-приложением на Angular 17 версии.
Keycloak потребует настройки, но благодаря детальной пошаговой документации этот процесс достаточно прост. В рамках данной статьи мы не будем его описывать, так как наш сервер используется только для иллюстрации рассматриваемых концепций без необходимости расширенной конфигурации.
В рамках данного руководства рассматривается интеграция с Angular 17-приложением. Первым шагом представим библиотеки для работы с Keycloak. В нашем проекте применялись две ключевые библиотеки:
- keycloak-js — ключевой компонент экосистемы Keycloak, предназначенный для защиты веб-приложений с использованием протокола OpenID Connect. Эта клиентская JavaScript-библиотека обеспечивает аутентификацию и авторизацию пользователей в веб-приложениях. Благодаря гибкости и простоте интеграции, keycloak-js активно используется во множестве проектов, что подтверждается статистикой npm-регистра. Регулярные обновления библиотеки свидетельствуют о активной поддержке со стороны сообщества. Кроме того, библиотека имеет встроенную поддержку приложений Cordova, что расширяет её применение на мобильные приложения.
- keycloak-angular — библиотека-обёртка, упрощающая использование keycloak-js в Angular-приложениях. Она расширяет функциональность базовой библиотеки, добавляя новые методы для более удобной интеграции с Angular. Также предоставляет базовую реализацию AuthGuard, позволяющую кастомизировать логику аутентификации. Дополнительно доступна возможность использования HttpClient Interceptor, который автоматически добавляет Authorization-заголовок к указанным HTTP-запросам.
Требования нашего проекта.
Для данной реализации необходимо:
- Реализовать систему входа/выхода через Keycloak;
- Обеспечить базовую интеграцию с Keycloak.
Основные сценарии работы:
- Разграничение доступа к страницам;
- Авторизация через Keycloak;
- Получение данных пользователя;
- Защита маршрутов;
- Автоматический редирект на логин;
- Страница подтверждения выхода.
Инициализация приложения и настройка Keycloak.
Для начала работы загрузите образ Keycloak для Docker с официального сайта. Чтобы запустить Keycloak в локальном окружении, просто выполните следующую команду:
docker run -p 8080:8080 -e KEYCLOAK_ADMIN=admin -e KEYCLOAK_ADMIN_PASSWORD=admin quay.io/keycloak/keycloak:23.0.4 start-dev
После выполнения этой команды вы можете перейти в панель управления Keycloak по адресу http://localhost:8080. Для выполнения необходимой настройки следуйте инструкциям на официальном сайте Keycloak. После внесения всех изменений оставьте процесс Keycloak работающим в консоли - это обеспечит возможность взаимодействия с сервером.
В рамках этого руководства мы использовали realm Pretius-Keycloak-FE-Integration с client id keycloak-angular-integration-tutorial.
Для фронтенд-приложения необходимо создать новый Angular-проект на базе фреймворка Angular 17. Для этого выполните следующие действия:
ng new keycloak-fe-integration
Далее система запросит информацию о функциях, которые необходимо включить в начальную конфигурацию. Выберите подходящие опции в соответствии с вашими требованиями или оставьте значения по умолчанию.
В нашем проекте используются следующие версии библиотек:
- keycloak-js — версия 22;
- keycloak-angular — версия 15.
npm install keycloak-js@22 keycloak-angular@15 --save
После установки необходимых npm-пакетов и других зависимостей, требуемых Angular, вы увидите рабочее пространство с корневой директорией под названием keycloak-fe-integration (если вы использовали такое же имя, как у нас). Следующим шагом будет замена существующей файловой структуры на указанную ниже:
Все страницы структурированы в директории pages по функциональному признаку. Прежде чем анализировать бизнес-логику, рассмотрим состав данной папки, содержащей компоненты страниц приложения.
Стартовая страница (main-page), открываемая при первом запуске, требует особого внимания. Перейдём к анализу её логики:
@Component({
selector: 'app-main-page',
standalone: true,
imports: [NgIf],
styleUrl: './main-page.component.scss',
template: `<div class="content">
<span class="title">Main page</span>
</div>
<a
*ngIf="!isLoggedIn"
role="button"
class="button"
(click)="redirectToLoginPage()"
>
Log In
</a>`,
})
export class MainPageComponent {
get isLoggedIn(): boolean {
return this.authenticationService.isLoggedIn();
}
constructor(private readonly authenticationService: AuthenticationService) {}
redirectToLoginPage(): void {
this.authenticationService.redirectToLoginPage();
}
}
Данный компонент выполняет только две функции:
- Отображает в заголовке информацию о текущем местоположении пользователя;
- Предоставляет кнопку для входа через Keycloak.
Следующий компонент для анализа - unprotected-route:
@Component({
selector: 'app-unprotected-route',
standalone: true,
imports: [CommonModule],
template: `
<div class="content">
<span class="title">Welcome {{ username }}!</span>
<span class="status-text">This is an unprotected route</span>
</div>
`
})
export class UnprotectedRouteComponent {
get username(): string {
return this.authenticationService.isLoggedIn()
? this.authenticationService.userName
: 'friend';
}
constructor(readonly authenticationService: AuthenticationService) {}
}
Как видите, этот компонент отвечает только за отображение имени пользователя при его авторизации. В противном случае выводится стандартная строка-заглушка.
При анализе компонента protected-route становится очевидным, что его функциональность ограничена выводом базовых данных.
@Component({
selector: 'app-protected-route',
standalone: true,
template: `
<div class="content">
<span class="title">Welcome {{ authenticationService.userName }}!</span>
<span class="status-text">This is a protected route</span>
</div>
`
})
export class ProtectedRouteComponent {
constructor(readonly authenticationService: AuthenticationService) {}
}
Кроме того, в последних двух компонентах содержится лишь простой код, отвечающий за отображение чистого HTML. Ниже представлено содержимое папки not-found:
@Component({
selector: 'app-not-found',
standalone: true,
template: `<h1>Page not found</h1>`
})
export class NotFoundComponent {}
А вот экран выхода из системы, который отображается при выходе пользователя из приложения:
@Component({
selector: 'app-logout-screen',
standalone: true,
template: `
<div class="content">
<span class="title">You have been logged out!</span>
</div>`
})
export class LogoutScreenComponent {}
Анализ представленных фрагментов кода показывает, что отдельные страницы не участвуют в обмене данными между Keycloak и клиентской частью. В соответствии с принципами использованными при создании данного руководства, бизнес-логика вынесена из компонентов и реализована в:
- Системе защиты доступа (Guards);
- Специальном сервисе для взаимодействия с Keycloak.
В следующих разделах будут рассмотрены ключевые компоненты системы и их функции.
Настройка Keycloak.
После успешной установки библиотеки keycloak-angular можно приступить к настройке модуля Keycloak во frontend-приложении.
- Определение переменных окружения. Сначала необходимо задать переменные окружения, используемые в приложении. Для этого создайте файл (например, environment.ts) в корневой директории проекта.
- Пример конфигурации. Ниже приведены переменные, которые использовались в нашем проекте:
export const environment = {
production: false,
keycloak: {
authority: 'http://localhost:8080',
redirectUri: 'http://localhost:4200',
postLogoutRedirectUri: 'http://localhost:4200/logout',
realm: 'Pretius-Keycloak-FE-Integration',
clientId: 'keycloak-angular-integration-tutorial',
},
idleConfig: { idle: 10, timeout: 60, ping: 10 },
};
Данные переменные хранят конфигурационные параметры сервера Keycloak, используемые для подключения Angular-приложения. Их детальное описание представлено в следующих разделах.
Чтобы гарантировать инициализацию Keycloak во время запуска приложения:
- Добавьте провайдер с токеном APP_INITIALIZER;
- Реализуйте функцию инициализации для выполнения в runtime;
- Зарегистрируйте её в корневом модуле (AppModule).
Данный подход позволяет выполнять настройку Keycloak с требуемой конфигурацией. В нашей реализации использовалась модульная архитектура. Ниже представлено содержимое соответствующего модуля:
export const initializeKeycloak = (keycloak: KeycloakService) => async () =>
keycloak.init({
config: {
url: environment.keycloak.authority,
realm: environment.keycloak.realm,
clientId: environment.keycloak.clientId,
},
loadUserProfileAtStartUp: true,
initOptions: {
onLoad: 'check-sso',
silentCheckSsoRedirectUri:
window.location.origin + '/assets/silent-check-sso.html',
checkLoginIframe: false,
redirectUri: environment.keycloak.redirectUri,
},
});
@NgModule({
bootstrap: [AppComponent],
declarations: [AppComponent],
imports: [
RouterModule.forRoot(routes),
BrowserModule,
KeycloakAngularModule,
CommonModule,
HttpClientModule,
NavigationBarComponent,
FooterComponent,
],
providers: [
{
provide: APP_INITIALIZER,
useFactory: initializeKeycloak,
multi: true,
deps: [KeycloakService],
},
provideUserIdleConfig({
idle: environment.idleConfig.idle,
ping: environment.idleConfig.ping,
timeout: environment.idleConfig.timeout,
}),
],
})
export class AppModule {}
Данный код позволяет настроить поддержку Keycloak в приложении. Ключевой здесь является функция initializeKeycloak(), которая отвечает за корректную инициализацию сервиса. Необходимо убедиться, что передаваемые в неё данные точно соответствуют конфигурации в Keycloak. Для этого укажите следующие значения в config-объекте:
- url - путь к нашему серверу Keycloak (по умолчанию localhost:8080);
- realm - имя созданного в Keycloak realm'а;
- clientId - название клиента, назначенного нашему приложению в Keycloak, который будет выполнять запросы аутентификации пользователей.
Также необходимо установить параметр loadUserProfileAtStartUp, чтобы приложение загружало данные текущего аутентифицированного пользователя при старте. Без этой настройки после входа пользователя его данные будут недоступны - для работы с ними потребуется их ручная загрузка.
Следующий объект, который необходимо настроить - initOptions. Это объект конфигурации, позволяющий кастомизировать работу и инициализацию Keycloak. Устанавливаемые атрибуты включают:
1. onLoad - Принимает два значения:
-
login-required - принудительная аутентификация при запуске приложения:
- Если пользователь авторизован в Keycloak - получает доступ к приложению;
- Если не авторизован - отображается страница входа.
- check-sso - позволяет вручную управлять статусом аутентификации и перенаправлениями.
2. checkLoginIframe - Определяет, должен ли Keycloak проверять статус авторизации через iframe. Требует осторожного использования - некорректная настройка может вызывать постоянные перезагрузки страницы.
3. redirectUri - URI (Uniform Resource Identifier) для перенаправления пользователя после успешного входа.
4. silentCheckSsoRedirectUri - Позволяет проверять статус SSO в фоновом режиме:
- Без прямого перенаправления на сервер Keycloak и обратно;
- Проверка выполняется в скрытом iframe.
Значение window.location.origin + /assets/silent-check-sso.html указывает на расположение статического HTML-файла, используемого для проверки Silent SSO. Этот файл обеспечивает взаимодействие между приложением и Keycloak через iframe.
Для реализации:
- 1. Создайте данный файл по указанному пути;
- 2. Добавьте в него следующий код:
<html>
<body>
<script>
parent.postMessage(location.href, location.origin);
</script>
</body>
</html>
Доступные варианты могут отличаться в зависимости от версии Keycloak и особенностей приложения. Для получения актуальной информации обратитесь к официальной документации Keycloak.
Настройка сервиса аутентификации
После корректной инициализации и настройки модуля для работы с Keycloak вы сможете использовать предоставляемые им функции. Для этого создайте отдельный сервис authorization.service.ts, который будет содержать необходимые методы для работы с Keycloak в приложении.
Поскольку требуется обеспечить единственный экземпляр этого сервиса в приложении (singleton), используйте стандартный подход при создании сервисов через Angular CLI, оставив декоратор @Injectable({ providedIn: 'root' }). Затем в конструкторе внедрите сервис из библиотеки keycloak-angular:
constructor(private readonly keycloakService: KeycloakService) {}
После подготовки сервиса перейдем к реализации нескольких полезных методов. Первым будет метод для входа пользователей в приложение. Создайте метод redirectToLoginPage():
redirectToLoginPage(): Promise<void> {
this.keycloakService.login();
}
Теперь перейдем к реализации оставшихся методов. Для данного проекта потребуется:
- Получение информации о имени пользователя;
- Проверка статуса авторизации;
- Реализация выхода из системы.
get userName(): string {
return this.keycloakService.getUsername();
}
isLoggedIn(): boolean {
return this.keycloakService.isLoggedIn();
}
logout(): void {
this.keycloakService.logout(environment.keycloak.postLogoutRedirectUri);
}
Как видите, API, предоставляемое keycloak-angular, позволяет легко реализовать все эти требования через вызов соответствующих методов на стороне Keycloak. Особое внимание стоит уделить методу logout(), в который можно передать параметр, определяющий путь для перенаправления пользователя после выхода из приложения. Как и ранее, этот параметр задаётся через переменные окружения.
Для реализации автоматического выхода пользователя после периода неактивности можно вызвать метод logout(), используя внешнюю библиотеку для отслеживания активности. Продемонстрируем это на примере библиотеки angular-user-idle.
После настройки (согласно официальной документации модуля) добавьте следующий код в файл app.component.ts:
ngOnInit(): void {
if (this.authenticationService.isLoggedIn()) {
this.userIdleService.startWatching();
this.userIdleService
.onTimerStart()
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe();
this.userIdleService
.onTimeout()
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe(() => {
alert('Your session has timed out. Please log in again.');
this.authenticationService.logout();
this.userIdleService.resetTimer();
});
}
}
Представленная функциональность отслеживает активность пользователя только после успешного входа в систему. Данный подход позволяет:
- Автоматически выходить из приложения по истечении заданного времени бездействия;
- Отображать соответствующее уведомление о выходе.
Важное замечание: Таймер необходимо сбрасывать при каждом вызове этой функции. В противном случае уведомление будет появляться постоянно, блокируя возможность выхода пользователя.
Маршрутизация приложения (Application Routing)
После интеграции с сервером Keycloak следующим шагом будет настройка маршрутизации приложения и реализация контроля доступа к отдельным страницам. Для этого проанализируем файл app.routes.ts, где:
- Перечислены все маршруты приложения;
- Для каждого роута настроен контроль доступа через guard canActivate.
export enum AppRoutes {
Main = '',
Protected = 'protected',
Unprotected = 'unprotected',
Logout = 'logout',
NotFound = '404',
}
export const routes: Routes = [
{
path: AppRoutes.Main,
pathMatch: 'full',
component: MainPageComponent,
},
{
path: AppRoutes.Protected,
canActivate: [AuthGuard],
component: ProtectedRouteComponent,
},
{
path: AppRoutes.Unprotected,
component: UnprotectedRouteComponent,
},
{
path: AppRoutes.Logout,
canActivate: [LogoutRouteGuard],
component: LogoutScreenComponent,
},
{
path: AppRoutes.NotFound,
component: NotFoundComponent,
},
{
path: '**',
redirectTo: AppRoutes.NotFound,
},
];
Все маршруты нашего приложения представлены выше. При анализе кода обратите внимание на ключевые элементы, отвечающие за логику авторизации.
Для ограничения доступа к определённым страницам используйте guards (защитники маршрутов) — они предотвратят доступ неавторизованных пользователей.
Пример такой страницы — Protected Page, защищённая AuthGuard. Давайте разберём принцип работы этого guard'а:
export const AuthGuard: CanActivateFn = (): boolean => {
const authenticationService = inject(AuthenticationService);
if (authenticationService.isLoggedIn()) {
return true;
}
authenticationService.redirectToLoginPage();
return false;
};
Принцип работы guard'а:
-
При попытке доступа к защищённой странице guard проверяет авторизацию пользователя через метод isLoggedIn()
-
Метод определяет наличие активной сессии:
- Если сессия активна ? разрешает доступ;
- Если сессии нет ? перенаправляет на страницу входа.
Особенности реализации:
- Для страницы, появляющейся после выхода (logout), также можно применить guard;
- Это ограничит доступ для пользователей с активной сессией Keycloak;
- Проверка осуществляется тем же методом isLoggedIn() из authorizationService.
export const LogoutRouteGuard: CanActivateFn = () => {
const authenticationService = inject(AuthenticationService);
const router = inject(Router);
if (!authenticationService.isLoggedIn()) {
return true;
} else {
return router.createUrlTree([AppRoutes.Main]);
}
};
Если Вы все сделали как опсиано в статье:
- Откройте главную страницу приложения без авторизации
- Запустите приложение командой:
ng serve