Разработка OIDC приложений
В этом разделе представлено практическое руководство по разработке приложений, интегрированных с IDENTYX через протокол OpenID Connect. Вы найдете примеры кода на различных языках программирования, рекомендации по выбору библиотек и лучшие практики реализации.
Подготовка к разработке
Перед началом разработки убедитесь, что у вас есть:
- Зарегистрированное приложение в IDENTYX — перейдите в раздел Создание OIDC приложения для регистрации
- Client ID и Client Secret — полученные при регистрации приложения
- Redirect URI — URL вашего приложения для обработки обратного вызова после аутентификации
- Базовый URL IDENTYX сервера — адрес вашего развернутого экземпляра IDENTYX
Никогда не публикуйте client_secret в открытых репозиториях, клиентском коде или конфигурационных файлах, доступных публично. Храните секреты в переменных окружения или защищенных хранилищах.
Выбор библиотеки OIDC
Для упрощения интеграции рекомендуется использовать готовые библиотеки OpenID Connect для вашего языка программирования. Эти библиотеки берут на себя сложности протокола, обработку токенов и валидацию.
Рекомендуемые библиотеки
| Язык/Фреймворк | Библиотека | Ссылка |
|---|---|---|
| PHP | jumbojett/openid-connect-php | GitHub |
| Python | authlib | GitHub |
| Node.js | openid-client | GitHub |
| Java/Spring | Spring Security OAuth2 | Официальный сайт |
| C# / .NET | IdentityModel.OidcClient | GitHub |
| Go | coreos/go-oidc | GitHub |
| Ruby | omniauth-openid-connect | GitHub |
Большинство современных библиотек поддерживают автоматическое обнаружение конфигурации через эндпоинт /.well-known/openid-configuration, что значительно упрощает настройку.
Пример на PHP
Рассмотрим пример интеграции с использованием популярной библиотеки jumbojett/openid-connect-php.
Установка
Установите библиотеку через Composer:
composer require jumbojett/openid-connect-php
Базовая конфигурация
<?php
require 'vendor/autoload.php';
use Jumbojett\OpenIDConnectClient;
// Создаем клиент OIDC
$oidc = new OpenIDConnectClient(
'https://ваш-identyx-сервер', // URL провайдера
'ваш_client_id', // Client ID
'ваш_client_secret' // Client Secret
);
// Настраиваем redirect URI
$oidc->setRedirectURL('https://ваше-приложение/callback');
// Запрашиваем необходимые scopes
$oidc->addScope(['openid', 'profile', 'email', 'permissions']);
// Выполняем аутентификацию
$oidc->authenticate();
// Получаем информацию о пользователе
$user_info = $oidc->requestUserInfo();
// Выводим данные пользователя
echo "Пользователь: " . $user_info->name . "<br>";
echo "Email: " . $user_info->email . "<br>";
echo "ID: " . $user_info->sub . "<br>";
// Получаем права доступа
$permissions = $user_info->permissions ?? [];
echo "Права: " . implode(', ', $permissions);
Расширенный пример с обработкой сессии
<?php
session_start();
require 'vendor/autoload.php';
use Jumbojett\OpenIDConnectClient;
// Проверяем, авторизован ли пользователь
if (isset($_SESSION['user_info'])) {
// Пользователь уже авторизован
$user_info = $_SESSION['user_info'];
echo "Добро пожаловать, " . htmlspecialchars($user_info['name']);
exit;
}
try {
$oidc = new OpenIDConnectClient(
getenv('IDENTYX_URL'),
getenv('CLIENT_ID'),
getenv('CLIENT_SECRET')
);
$oidc->setRedirectURL('https://ваше-приложение/callback');
$oidc->addScope(['openid', 'profile', 'email', 'permissions']);
// Аутентификация
$oidc->authenticate();
// Получаем информацию о пользователе
$user_info = $oidc->requestUserInfo();
// Сохраняем в сессию
$_SESSION['user_info'] = [
'sub' => $user_info->sub,
'name' => $user_info->name,
'email' => $user_info->email,
'permissions' => $user_info->permissions ?? []
];
// Сохраняем токены для последующего обновления
$_SESSION['access_token'] = $oidc->getAccessToken();
$_SESSION['refresh_token'] = $oidc->getRefreshToken();
// Перенаправляем на главную страницу
header('Location: /');
exit;
} catch (Exception $e) {
echo "Ошибка аутентификации: " . htmlspecialchars($e->getMessage());
exit;
}
Пример на Python
Пример интеграции с использованием библиотеки authlib и Flask фреймворка.
Установка
pip install authlib flask requests
Код приложения
from flask import Flask, redirect, url_for, session
from authlib.integrations.flask_client import OAuth
import os
app = Flask(__name__)
app.secret_key = os.urandom(24)
# Настройка OAuth
oauth = OAuth(app)
identyx = oauth.register(
name='identyx',
client_id=os.getenv('CLIENT_ID'),
client_secret=os.getenv('CLIENT_SECRET'),
server_metadata_url='https://ваш-identyx-сервер/.well-known/openid-configuration',
client_kwargs={
'scope': 'openid profile email permissions'
}
)
@app.route('/')
def home():
user = session.get('user')
if user:
return f'Привет, {user["name"]}! <a href="/logout">Выход</a>'
return '<a href="/login">Войти через IDENTYX</a>'
@app.route('/login')
def login():
redirect_uri = url_for('callback', _external=True)
return identyx.authorize_redirect(redirect_uri)
@app.route('/callback')
def callback():
token = identyx.authorize_access_token()
user_info = token.get('userinfo')
# Сохраняем информацию о пользователе в сессию
session['user'] = {
'sub': user_info['sub'],
'name': user_info['name'],
'email': user_info['email'],
'permissions': user_info.get('permissions', [])
}
session['token'] = token
return redirect('/')
@app.route('/logout')
def logout():
session.clear()
return redirect('/')
@app.route('/api/protected')
def protected():
# Пример защищенного эндпоинта
user = session.get('user')
if not user:
return {'error': 'Unauthorized'}, 401
# Проверяем права доступа
required_permission = '/api:/read'
if required_permission not in user.get('permissions', []):
return {'error': 'Forbidden'}, 403
return {'message': 'Доступ разрешен', 'user': user['name']}
if __name__ == '__main__':
app.run(debug=True, port=5000)
Пример на Node.js
Пример интеграции с использованием библиотеки openid-client и Express фреймворка.
Установка
npm install express express-session openid-client
Код приложения
const express = require('express');
const session = require('express-session');
const { Issuer } = require('openid-client');
const app = express();
// Настройка сессий
app.use(session({
secret: 'your-secret-key',
resave: false,
saveUninitialized: true,
cookie: { secure: false } // Установите true при использовании HTTPS
}));
// Инициализация OIDC клиента
let client;
(async () => {
const identyxIssuer = await Issuer.discover('https://ваш-identyx-сервер/.well-known/openid-configuration');
client = new identyxIssuer.Client({
client_id: process.env.CLIENT_ID,
client_secret: process.env.CLIENT_SECRET,
redirect_uris: ['http://localhost:3000/callback'],
response_types: ['code'],
});
console.log('OIDC клиент инициализирован');
})();
// Главная страница
app.get('/', (req, res) => {
if (req.session.user) {
res.send(`
<h1>Привет, ${req.session.user.name}!</h1>
<p>Email: ${req.session.user.email}</p>
<p>Права: ${req.session.user.permissions.join(', ')}</p>
<a href="/logout">Выход</a>
`);
} else {
res.send('<a href="/login">Войти через IDENTYX</a>');
}
});
// Начало аутентификации
app.get('/login', (req, res) => {
const authorizationUrl = client.authorizationUrl({
scope: 'openid profile email permissions',
state: 'random_state_value',
});
res.redirect(authorizationUrl);
});
// Обработка callback
app.get('/callback', async (req, res) => {
try {
const params = client.callbackParams(req);
const tokenSet = await client.callback('http://localhost:3000/callback', params, {
state: 'random_state_value',
});
// Получаем информацию о пользователе
const userinfo = await client.userinfo(tokenSet.access_token);
// Сохраняем в сессию
req.session.user = {
sub: userinfo.sub,
name: userinfo.name,
email: userinfo.email,
permissions: userinfo.permissions || []
};
req.session.tokens = {
access_token: tokenSet.access_token,
refresh_token: tokenSet.refresh_token
};
res.redirect('/');
} catch (err) {
console.error('Ошибка аутентификации:', err);
res.status(500).send('Ошибка аутентификации');
}
});
// Выход
app.get('/logout', (req, res) => {
req.session.destroy();
const logoutUrl = `https://ваш-identyx-сервер/api/service/oidc/end-session?client_id=${process.env.CLIENT_ID}&post_logout_redirect_uri=http://localhost:3000/`;
res.redirect(logoutUrl);
});
// Защищенный эндпоинт
app.get('/api/data', (req, res) => {
if (!req.session.user) {
return res.status(401).json({ error: 'Unauthorized' });
}
// Проверка прав доступа
const requiredPermission = '/data:/read';
if (!req.session.user.permissions.includes(requiredPermission)) {
return res.status(403).json({ error: 'Forbidden' });
}
res.json({ message: 'Данные доступны', user: req.session.user.name });
});
app.listen(3000, () => {
console.log('Сервер запущен на http://localhost:3000');
});
Работа с токенами
Access Token
Access Token используется для доступа к защищенным ресурсам. Срок действия токена ограничен (обычно 1 час).
Используйте Access Token в заголовке запросов к защищенным эндпоинтам:
Authorization: Bearer ваш_access_token
Обновление токенов с Refresh Token
Когда Access Token истекает, используйте Refresh Token для получения нового набора токенов без повторной аутентификации пользователя.
Пример на PHP:
<?php
function refreshAccessToken($refresh_token) {
$data = [
'grant_type' => 'refresh_token',
'refresh_token' => $refresh_token,
'client_id' => getenv('CLIENT_ID'),
'client_secret' => getenv('CLIENT_SECRET')
];
$ch = curl_init('https://ваш-identyx-сервер/api/service/oidc/token');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data));
$response = curl_exec($ch);
curl_close($ch);
$result = json_decode($response, true);
return [
'access_token' => $result['access_token'],
'refresh_token' => $result['refresh_token'],
'expires_in' => $result['expires_in']
];
}
// Использование
$new_tokens = refreshAccessToken($_SESSION['refresh_token']);
$_SESSION['access_token'] = $new_tokens['access_token'];
$_SESSION['refresh_token'] = $new_tokens['refresh_token'];
ID Token и JWT
ID Token — это JWT (JSON Web Token), содержащий информацию о пользователе. Вы можете декодировать его для получения базовых данных без дополнительных запросов.
Пример декодирования JWT на JavaScript:
function parseJwt(token) {
const base64Url = token.split('.')[1];
const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
const jsonPayload = decodeURIComponent(
atob(base64).split('').map(c =>
'%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2)
).join('')
);
return JSON.parse(jsonPayload);
}
const idToken = '...'; // ID Token из IDENTYX
const claims = parseJwt(idToken);
console.log('User ID:', claims.sub);
console.log('Name:', claims.name);
console.log('Email:', claims.email);
console.log('Issued at:', new Date(claims.iat * 1000));
console.log('Expires at:', new Date(claims.exp * 1000));
Всегда проверяйте подпись JWT перед использованием данных из токена в production. Большинство OIDC библиотек делают это автоматически.
Работа с правами доступа (permissions)
IDENTYX передает права доступа пользователя через scope permissions. Права представлены в формате /объект:/действие.
Проверка прав доступа
Пример проверки прав на PHP:
<?php
function hasPermission($user_permissions, $required_permission) {
// Точное совпадение
if (in_array($required_permission, $user_permissions)) {
return true;
}
// Проверка wildcard прав
foreach ($user_permissions as $permission) {
// Разбираем право на объект и действие
list($obj, $action) = explode(':', $permission);
list($req_obj, $req_action) = explode(':', $required_permission);
// Проверяем wildcard
if (($obj === '*' || $obj === $req_obj || str_ends_with($obj, '/*') && str_starts_with($req_obj, rtrim($obj, '*'))) &&
($action === '*' || $action === $req_action)) {
return true;
}
}
return false;
}
// Использование
$user_permissions = $_SESSION['user_info']['permissions'];
if (hasPermission($user_permissions, '/documents:/read')) {
echo "Доступ к чтению документов разрешен";
} else {
echo "Доступ запрещен";
}
Пример проверки прав на Python:
def has_permission(user_permissions, required_permission):
"""Проверяет наличие права у пользователя с учетом wildcard"""
# Точное совпадение
if required_permission in user_permissions:
return True
# Проверка wildcard
req_obj, req_action = required_permission.split(':')
for permission in user_permissions:
obj, action = permission.split(':')
# Проверяем соответствие объекта
obj_match = (
obj == '*' or
obj == req_obj or
(obj.endswith('/*') and req_obj.startswith(obj[:-1]))
)
# Проверяем соответствие действия
action_match = action == '*' or action == req_action
if obj_match and action_match:
return True
return False
# Использование
from flask import session, abort
@app.route('/documents')
def documents():
user = session.get('user')
if not user:
abort(401)
if not has_permission(user['permissions'], '/documents:/read'):
abort(403)
return "Список документов"
Получение актуальной информации о пользователе
Для получения актуальной информации о пользователе используйте эндпоинт /api/service/oidc/userinfo.
Пример запроса:
GET https://ваш-identyx-сервер/api/service/oidc/userinfo
Authorization: Bearer ваш_access_token
Пример ответа:
{
"sub": "user_12345",
"name": "Иванов Иван Иванович",
"email": "ivanov@example.com",
"email_verified": true,
"phone": "+79001234567",
"phone_verified": false,
"permissions": [
"/documents:/read",
"/documents:/write",
"/reports:/read"
]
}
Управление сессиями
Завершение сессии
Для корректного завершения сессии пользователя используйте эндпоинт /api/service/oidc/end-session.
Пример на PHP:
<?php
session_start();
// Очищаем локальную сессию
session_destroy();
// Перенаправляем на эндпоинт завершения сессии IDENTYX
$logout_url = 'https://ваш-identyx-сервер/api/service/oidc/end-session?' . http_build_query([
'post_logout_redirect_uri' => 'https://ваше-приложение/',
'client_id' => getenv('CLIENT_ID')
]);
header("Location: $logout_url");
exit;
Обработка истечения сессии
Access Token имеет ограниченный срок действия (по умолчанию 1 час). Когда токен истекает, вам нужно:
- Использовать Refresh Token для получения нового Access Token
- Если Refresh Token также истек — перенаправить пользователя на повторную аутентификацию
Пример обработки истечения токена:
<?php
function getValidAccessToken() {
// Проверяем срок действия текущего токена
if (time() >= $_SESSION['token_expires_at']) {
// Токен истек, пытаемся обновить
try {
$new_tokens = refreshAccessToken($_SESSION['refresh_token']);
$_SESSION['access_token'] = $new_tokens['access_token'];
$_SESSION['refresh_token'] = $new_tokens['refresh_token'];
$_SESSION['token_expires_at'] = time() + $new_tokens['expires_in'];
} catch (Exception $e) {
// Refresh token тоже истек, требуется повторная аутентификация
header('Location: /login');
exit;
}
}
return $_SESSION['access_token'];
}
Функция Backchannel Logout (автоматическое завершение сессий во всех приложениях) в настоящее время находится в разработке. Для приложений, работающих через IDENTYX Proxy, завершение сессии обрабатывается автоматически.
Лучшие практики
- Храните
client_secretв переменных окружения - Используйте HTTPS для всех запросов
- Проверяйте подпись JWT токенов
- Валидируйте
stateиnonceпараметры - Устанавливайте короткий срок жизни сессий
- Кешируйте публичные ключи для проверки JWT
- Обновляйте токены заранее, до истечения
- Используйте connection pooling для HTTP запросов
- Кешируйте информацию о пользователе на 5-10 минут
- Обрабатывайте истечение токенов gracefully
- Логируйте все ошибки аутентификации
- Показывайте понятные сообщения пользователям
- Реализуйте retry логику для сетевых ошибок
- Логируйте успешные входы и выходы
- Фиксируйте неудачные попытки аутентификации
- Не логируйте токены и секреты
- Отправляйте критичные события в журнал IDENTYX
Тестирование интеграции
Локальное тестирование
Для тестирования на локальной машине:
- Используйте
http://localhostв redirect_uri - Добавьте этот URL в список разрешенных в настройках приложения IDENTYX
- Если IDENTYX использует HTTPS, убедитесь что сертификат доверенный
Тестовые сценарии
Обязательно протестируйте следующие сценарии:
- Первичный вход — пользователь впервые входит в приложение
- Повторный вход — пользователь входит повторно (SSO)
- Истечение Access Token — проверка обновления через Refresh Token
- Выход из системы — корректное завершение локальной и удаленной сессий
- Backchannel Logout — выход инициирован из другого приложения
- Проверка прав — доступ к ресурсам согласно permissions
- Обработка ошибок — неверный client_secret, истекший refresh_token
Типичные проблемы
| Проблема | Причина | Решение |
|---|---|---|
| redirect_uri_mismatch | URL перенаправления не совпадает с зарегистрированным | Проверьте настройки приложения в IDENTYX и URL в коде |
| invalid_client | Неверный client_id или client_secret | Убедитесь что используете правильные учетные данные |
| invalid_grant | Код авторизации истек или уже использован | Коды одноразовые и действуют 10 минут. Получите новый код |
| CORS ошибки | Попытка вызова OIDC эндпоинтов с клиентской стороны | Все операции с токенами должны выполняться на сервере |
| Пустой permissions | Не запрошен scope permissions | Добавьте 'permissions' в список scopes при аутентификации |
Миграция существующего приложения
Если у вас уже есть приложение с собственной системой аутентификации:
- Создайте механизм миграции — импортируйте пользователей в IDENTYX или настройте LDAP интеграцию
- Реализуйте параллельную работу — временно поддерживайте оба способа входа
- Мигрируйте пользователей постепенно — дайте время адаптироваться
- Обновите проверки прав — используйте permissions из IDENTYX
- Удалите старую систему — после полной миграции всех пользователей
Теперь у вас есть все необходимое для интеграции вашего приложения с IDENTYX. Начните с простого примера для вашего языка программирования и постепенно добавляйте дополнительные возможности.
Связанные разделы
- 31. Обзор OIDC — теоретические основы протокола
- 32. Создание OIDC приложения — регистрация приложения в IDENTYX
- 54. OIDC интеграция — детальное описание протокола
- 56. API учетных записей — работа с сохраненными учетными данными
- 61. REST API — дополнительные возможности API