Настройка авторизации через OAUTH2 сервера https://oauth2.volmed.org.ru
Подготовительные операции
- Идем в БД oauth_db на сервере 172.16.130.31 и добавляем в таблицу oauth_clients
- client_id - Номер WEB интерфейса, который вы собираетесь подключить
- client_secret - Набор любых символов для шифрования
- Добавляем в настроечный файл сервиса следующие строки
$GLOBALS['id_resource'] = 7; // Номер WEB интерфейса $GLOBALS['jwt_key'] = 'sdklfwiomwefwepiojwepjowfmwfmwef'; // Строка с набором символов для шифрования JWT токена $oauth2_url = 'https://oauth2.volmed.org.ru'; // URL сервиса авторизации $GLOBALS['oauth2_url'] = $oauth2_url; $redirect_uri = $http."://$_SERVER[HTTP_HOST]"; $GLOBALS['redirect_uri'] = $redirect_uri;
- Для работы с JWT токеном используем библиотеку https://github.com/firebase/php-jwt. Ее нужно установить в каталог class/jwt
Создание таблицы для хранения дополнительных данных
Создаем таблицу
CREATE TABLE `user_sessions` ( `session_id` VARCHAR(64) NOT NULL COMMENT 'уникальная строка, хранится в JWT и БД' COLLATE 'utf8mb4_0900_ai_ci', `user_id` INT NOT NULL COMMENT 'идентификатор пользователя', `ip` VARCHAR(45) NULL DEFAULT NULL COMMENT 'опционально, помогает выявлять кражу сессии' COLLATE 'utf8mb4_0900_ai_ci', `user_agent_hash` VARCHAR(64) NULL DEFAULT NULL COMMENT 'опционально, помогает выявлять кражу сессии' COLLATE 'utf8mb4_0900_ai_ci', `created_at` DATETIME NULL DEFAULT NULL COMMENT 'время создания сессии', `last_activity` DATETIME NULL DEFAULT NULL, PRIMARY KEY (`session_id`) USING BTREE ) COLLATE='utf8mb4_0900_ai_ci' ENGINE=InnoDB ;
Класс для авторизации по Aouth2
<?php
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
class AuthSait
{
/*
* Класс по авторизации на сайт
*/
public array $auth;
public array $picture;
private string $ua_hash;
private int $jwt_ttl = 3600; // 1 час
private string $jwt_algo = 'HS256';
private $secure;
public function __construct()
{
// Проверяем, что есть объект работы с БД
if (!empty($GLOBALS['db']) && is_object($GLOBALS['db'])) {
} else {
return 'Нет подключения к БД сайта';
}
// Проверяем, что есть объект работы с БД пользователей
if (!empty($GLOBALS['lpu_user']) && is_object($GLOBALS['lpu_user'])) {
} else {
return 'Нет подключения к БД пользователей';
}
$this->auth = [];
$this->secure = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off');
$this->ua_hash = hash('sha256', $_SERVER['HTTP_USER_AGENT']); // Создаем hash от User_Agent
// Переменная с путями до картинок пользователей по умолчанию, в зависимости от пола
$this->picture = [
0 => '/users/pictures/user-m.png',
1 => '/users/pictures/user-m.png',
2 => '/users/pictures/user-w.png',
];
}
Основной метод авторизации
public function auth()
{
session_start();
$page = $_GET['page'] ?? '';
// Logout пользователя
if ($page === 'logout') {
$this->auth['logout'] = $this->logout(1);
return $this->auth;
}
// 1️⃣ Проверяем JWT cookie
if (!empty($_COOKIE['jwt'])) {
$this->test_cookie();
return $this->auth;
}
// 2️⃣ Если возвращаемся с сервера OAuth2 с code
if (!empty($_GET['code'])) {
$return = $this->test_code();
header("Location: $return");
exit;
}
// Если это первый вход, то переходим на страницу Авторизации
$url = $this->oauth2_login();
header("Location: $url");
exit;
}
Метод выхода из системы
public function logout($num)
{
/*
* Выход из системы
*/
// 2️⃣ Удаляем сессию на сервере (если есть)
if (!empty($_COOKIE['jwt'])) {
try {
$payload = \Firebase\JWT\JWT::decode($_COOKIE['jwt'], new \Firebase\JWT\Key($GLOBALS['jwt_key'], $this->jwt_algo));
if (!empty($payload->session_id)) {
$GLOBALS['db']->mysqli_qw(
'DELETE FROM user_sessions WHERE session_id = ?',
$payload->session_id
);
}
} catch (\Exception $e) {
// JWT невалиден → ничего не делаем
}
}
if (!empty($this->auth['error']) && !empty($this->auth['text_error'])) {
$this->auth['error'] .= $this->auth['text_error'];
}
// printr($this->auth);
foreach ($_COOKIE as $key => $val) {
if (!empty($_COOKIE[$key])) {
unset($_COOKIE[$key]);
setcookie($key, "", time() - 3600, '/');
}
}
$this->auth = [];
$this->oauth2_logout();
$logout = 1;
return $logout;
}
Формирование строки авторизации
Для перехода на страницу сервера авторизации, генерим следующий код
// 2. Если нет авторизации и нет code → редиректим на OAuth2 authorize
if (empty($this->auth['id']) && empty($oauth2_access['access_token'])) {
// Сохраняем текущий URL для возврата после авторизации
$_SESSION['oauth_return_to'] = $_SERVER['REQUEST_URI'];
// Генерируем state для защиты от CSRF
$state = bin2hex(random_bytes(16));
//old $_SESSION['state'] = $state;
$_SESSION['oauth_states'][$state] = time();
// printr($_SESSION);
// exit;
// Формируем URL для authorize
$url_data = [
'response_type' => 'code',
'client_id' => $GLOBALS['id_resource'],
'state' => $state,
'redirect_uri' => $GLOBALS['redirect_uri'],
// 'scope' => 'profile', // пока не используем
];
$url = $GLOBALS['oauth2_url'] . '?' . http_build_query($url_data);
header('Location:' . $url);
exit;
}
После перехода на сервер авторизации, вводим Логин и Пароль. И если он правильный, то сервер возвращает на страницу редиректа
Если пришёл code от OAuth2 сервера
// === OAuth2 Flow ===
// 1. Если пришёл code от OAuth2 сервера
if (!empty($_GET['code'])) {
$validState = false;
if (!empty($_GET['state']) && !empty($_SESSION['oauth_states'])) {
$ttl = 600; // 10 минут
// очистка старых state
foreach ($_SESSION['oauth_states'] as $s => $t) {
if ($t < time() - $ttl) {
unset($_SESSION['oauth_states'][$s]);
}
}
// проверка текущего state
if (isset($_SESSION['oauth_states'][$_GET['state']])) {
$validState = true;
unset($_SESSION['oauth_states'][$_GET['state']]); // одноразовый
}
}
if ($validState) {
// Обмениваем code на access_token
$oauth2_access = $this->oauth2_post();
if (!empty($oauth2_access['id_user'])) {
$this->auth_from_cookie($oauth2_access['id_user']);
$this->set_cookie();
// Редирект на исходный URL
$redirect = $_SESSION['oauth_return_to'] ?? '/';
// Удаляем ненужные параметры
unset($_SESSION['oauth_return_to']);
header('Location: ' . $redirect);
exit;
} else {
die('Ошибка авторизации: не удалось получить access_token');
}
} else {
// возможно истёк или устарел
$redirect = $_SESSION['oauth_return_to'] ?? '/';
unset($_SESSION['oauth_return_to']);
header('Location: ' . $redirect);
exit;
}
}
Формирование POST запроса авторизации
private function oauth2_post()
{
$post_data = [
"user_id" => $_GET['user_id'], // ID пользователя, возвращенного oauth2 серверром
"code" => $_GET['code'],
'grant_type' => 'authorization_code', // Тип гранта
'client_id' => $GLOBALS['id_resource'], // ID сервиса
'client_secret' => 'wetreyrtbv:KJLKO', // это код из БД OUTH2 сервера таблица oauth_clients.client_secret для $GLOBALS['id_resource']
'redirect_uri' => $GLOBALS['redirect_uri'],
// 'scope' => 'read write',
];
$data_string = json_encode($post_data);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $GLOBALS['oauth2_url'] . '/token');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $data_string);
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
'Content-Type: application/json',
'Content-Length: ' . strlen($data_string),
));
$oauth2_access = [];
$output = curl_exec($ch);
// execute the request
if ($output === false) echo 'Ошибка curl: ' . curl_error($ch);
else {
$oauth2_access = json_decode($output, true);
if (!empty($oauth2_access['error'])) {
printr($oauth2_access);
exit();
}
$oauth2_access['id_user'] = $_GET['user_id'];
}
curl_close($ch);
return $oauth2_access;
}
Запрос пользовательских данных
public function take_userinfo()
{
$accessToken = $this->access_token; // <-- access token
$userInfoUrl = $GLOBALS['oauth2_url'] . '/userinfo'; // <-- endpoint
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $userInfoUrl);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer ' . $accessToken,
'Accept: application/json'
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if (curl_errno($ch)) {
echo 'Ошибка cURL: ' . curl_error($ch);
} elseif ($httpCode !== 200) {
echo "Ошибка HTTP $httpCode: $response";
} else {
$userData = json_decode($response, true);
//printr($userData);
}
curl_close($ch);
return $userData;
}