Настройка авторизации через OAUTH2 сервера https://oauth2.volmed.org.ru: различия между версиями

Материал из Wiki МИАЦ ВО
Перейти к навигации Перейти к поиску
Строка 30: Строка 30:
</pre>
</pre>


==Хранение пользовательских данных в COOKIES==
==Класс для авторизации по Aouth2==
Самое простое для моего случая - хранить в куках id пользователя и время создания куков. Тк все данные пользователя, я могу вытащить из БД по его id.<br>
Их нужно зашифровать с помощью JWT и хранить полученное значение в куках.
<pre>
<pre>
protected function set_cookie()
<?php
 
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
 
class AuthSait
{
{
/*
    /*
* Устанавливаем COOKIE
    *   Класс по авторизации на сайт
*/
    */
  if (empty($this->login_expires_set)) {
    public array $auth;
    $this->login_expires_set = 0;
    public array $picture;
  }
    private string $ua_hash;
  if (empty($_COOKIE['jwt']) && !empty($this->auth['id'])) {
    private int $jwt_ttl = 3600; // 1 час
    $payload = [
    private string $jwt_algo = 'HS256';
      'id_user' => !empty($this->auth['id_real']) ? $this->auth['id_real'] : $this->auth['id'],
    private $secure;
      'time_beg' => time(),
 
    ];
 
    setcookie('jwt', JWT::encode($payload, $GLOBALS['jwt_key'], 'HS256'), [
 
      "expires" => strtotime($this->login_expires_set),
    public function __construct()
      "path" => "/",
    {
      "httponly" => true,
        // Проверяем, что есть объект работы с БД
    ]);
        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',
        ];
    }
</pre>
</pre>
И проверка кукис
===Основной метод авторизации===
<pre>
<pre>
protected function load_cookie()
public function auth()
{
    {
  /*
        session_start();
   * Метод по проверке COOKIE и загрузки их в переменные для проверки авторизации
        $page = $_GET['page'] ?? '';
  */
       
  if (!empty($_COOKIE['jwt'])) {
        // Logout пользователя
      $decoded_obj = JWT::decode($_COOKIE['jwt'], new Key($GLOBALS['jwt_key'], 'HS256'));
        if ($page === 'logout') {
      $decoded = get_object_vars($decoded_obj);
            $this->auth['logout'] = $this->logout(1);
      $this->id_user = $decoded['id_user'];
            return $this->auth;
      $this->time_beg = $decoded['time_beg'];
        }
}
 
}
        // 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;
    }
</pre>
===Метод выхода из системы===
<pre>
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;
    }
</pre>
</pre>



Версия от 14:57, 10 марта 2026

Подготовительные операции

  1. Идем в БД oauth_db на сервере 172.16.130.31 и добавляем в таблицу oauth_clients
    1. client_id - Номер WEB интерфейса, который вы собираетесь подключить
    2. client_secret - Набор любых символов для шифрования
  2. Добавляем в настроечный файл сервиса следующие строки
$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;
  1. Для работы с 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;
}