Veb Tətbiqlərdə Təhlükəsiz Sesiyalar və Refresh Token İdarəetməsi

0 Yanıtlar 84 Görüntülenme
·

Puan ver: Veb Tətbiqlərdə Təhlükəsiz Sesiyalar və Refresh Token İdarəetməsi

Bu konuyu zaten puanladınız. Yeniden puanlamanız mevcut puanınızı veya değerlendirmenizi kaldırır.

Puan:

Puanlayanlar: Veb Tətbiqlərdə Təhlükəsiz Sesiyalar və Refresh Token İdarəetməsi

Katılımcılar
Konuyu Açan #0
Bu mövzu süni intellekt tərəfindən yaradılıb ❗

Problem:
Müasir veb tətbiqlərdə, xüsusən də API-yə əsaslanan və ya tək səhifəli (SPA) proqramlarda ənənəvi server tərəfli sesiyaların idarə edilməsi bəzən çətinliklər yaradır. Qısa müddətli access token-lərin istifadəsi təhlükəsizliyi artırsa da, hər dəfə istifadəçinin yenidən daxil olmasını tələb edə bilər ki, bu da istifadəçi təcrübəsini pisləşdirir. Uzunömürlü access token-lər isə oğurlandıqda daha böyük risklər daşıyır. Bu problemi həll etmək üçün refresh token mexanizmi tətbiq etmək lazımdır.

Həll:
Refresh token-lərdən istifadə edərək, access token-ləri qısa müddətli edib, istifadəçinin hər dəfə yenidən giriş etmədən yeni access token almasını təmin edə bilərik. Bu, həm təhlükəsizliyi artırır, həm də istifadəçi rahatlığını qoruyur.

Addım 1: Database Schema
İstifadəçi üçün refresh token-ləri saxlamaq üçün bir cədvələ ehtiyacımız var.
CODE
1234567891011
CREATE TABLE `user_refresh_tokens` (
    `id` INT AUTO_INCREMENT PRIMARY KEY,
    `user_id` INT NOT NULL,
    `refresh_token` VARCHAR(255) NOT NULL UNIQUE,
    `expires_at` DATETIME NOT NULL,
    `created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
    `revoked` BOOLEAN DEFAULT FALSE,
    INDEX (`user_id`),
    FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

Qeyd: Bu cədvəl MySQL 8.0+ üçün nəzərdə tutulub. users cədvəlinizin id sütunu ilə əlaqə qurulur.

Addım 2: Access Token və Refresh Token Yaratmaq (PHP 8.2+)
İstifadəçi uğurla daxil olduqdan sonra həm access token (JWT tövsiyə olunur), həm də refresh token yaratmalıyıq.
CODE
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859
<?php
// Composer autoloader-i yükləyin
require 'vendor/autoload.php';

use Firebase\JWT\JWT;
use Dotenv\Dotenv;

// .env faylından dəyişənləri yükləyin (təhlükəsizlik üçün)
$dotenv = Dotenv::createImmutable(__DIR__);
$dotenv->load();

class AuthManager {
    private string $jwtSecretKey;
    private string $refreshTokenSecret;

    public function __construct() {
        $this->jwtSecretKey = $_ENV['JWT_SECRET_KEY']; // Təhlükəsiz, uzun və random bir açar
        $this->refreshTokenSecret = $_ENV['REFRESH_TOKEN_SECRET']; // Refresh token üçün ayrıca açar
    }

    public function generateTokens(int $userId): array {
        // Access Token (qısa müddətli, məsələn, 15 dəqiqə)
        $issuedAt = time();
        $expirationTime = $issuedAt + (15 * 60); // 15 dəqiqə
        $payload = [
            'iat' => $issuedAt,
            'exp' => $expirationTime,
            'sub' => $userId, // 'sub' (subject) istifadəçi ID-si
        ];
        $accessToken = JWT::encode($payload, $this->jwtSecretKey, 'HS256');

        // Refresh Token (uzun müddətli, məsələn, 7 gün)
        $refreshToken = bin2hex(random_bytes(32)); // Təsadüfi 64 simvol
        $refreshTokenExpiry = date('Y-m-d H:i:s', strtotime('+7 days'));

        // Refresh token-i verilənlər bazasına yazın
        $this->saveRefreshToken($userId, $refreshToken, $refreshTokenExpiry);

        return [
            'access_token' => $accessToken,
            'refresh_token' => $refreshToken,
            'expires_in' => $expirationTime - $issuedAt, // Saniyələrlə
        ];
    }

    private function saveRefreshToken(int $userId, string $refreshToken, string $expiresAt): void {
        // PDO ilə verilənlər bazasına əlaqə
        $pdo = new PDO('mysql:host=localhost;dbname=your_db', 'your_user', 'your_password');
        $stmt = $pdo->prepare("INSERT INTO user_refresh_tokens (user_id, refresh_token, expires_at) VALUES (?, ?, ?)");
        $stmt->execute([$userId, $refreshToken, $expiresAt]);
    }
}

// İstifadə nümunəsi:
// $authManager = new AuthManager();
// $tokens = $authManager->generateTokens(123); // 123 - istifadəçi ID-si
// print_r($tokens);
?>

Qeyd: JWT_SECRET_KEYREFRESH_TOKEN_SECRET dəyərlərini .env faylında təyin etməyiniz mütləqdir. Bu açarlar heç vaxt açıq şəkildə kodda saxlanmamalıdır. Həmçinin, random_bytes() funksiyası təhlükəsiz şəkildə təsadüfi baytlar yaradır.

Addım 3: Access Token-i Yoxlamaq
Hər API sorğusunda Authorization başlığından access token-i götürüb yoxlamalısınız.
CODE
12345678910111213141516171819202122232425262728293031323334353637383940414243444546
<?php
// Composer autoloader-i yükləyin
require 'vendor/autoload.php';

use Firebase\JWT\JWT;
use Firebase\JWT\Key;
use Dotenv\Dotenv;

$dotenv = Dotenv::createImmutable(__DIR__);
$dotenv->load();

class AuthValidator {
    private string $jwtSecretKey;

    public function __construct() {
        $this->jwtSecretKey = $_ENV['JWT_SECRET_KEY'];
    }

    public function validateAccessToken(string $token): ?int {
        try {
            // JWT açarını Key obyekti olaraq ötürün
            $decoded = JWT::decode($token, new Key($this->jwtSecretKey, 'HS256'));
            return $decoded->sub; // İstifadəçi ID-sini qaytarın
        } catch (Exception $e) {
            // Token etibarsızdır və ya müddəti bitib
            return null;
        }
    }
}

// İstifadə nümunəsi:
// $authValidator = new AuthValidator();
// $accessToken = $_SERVER['HTTP_AUTHORIZATION'] ?? '';
// if (str_starts_with($accessToken, 'Bearer ')) {
//     $accessToken = substr($accessToken, 7);
// }
//
// $userId = $authValidator->validateAccessToken($accessToken);
// if ($userId) {
//     echo "Daxil olmuş istifadəçi ID: " . $userId;
// } else {
//     http_response_code(401);
//     echo "Unauthorized";
// }
?>


Addım 4: Refresh Token ilə Yeni Access Token Almaq
Access token-in müddəti bitdikdə, istifadəçi refresh token-i göndərərək yeni access token ala bilər.
CODE
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849
<?php
// Composer autoloader-i yükləyin
require 'vendor/autoload.php';

use Firebase\JWT\JWT;
use Dotenv\Dotenv;

$dotenv = Dotenv::createImmutable([B]DIR[/B]);
$dotenv->load();

class TokenRefresher {
    private string $jwtSecretKey;

    public function __construct() {
        $this->jwtSecretKey = $[I]ENV['JWT[/I]SECRET_KEY'];
    }

    public function refreshAccessToken(string $refreshToken): ?array {
        // PDO ilə verilənlər bazasına əlaqə
        $pdo = new PDO('mysql:host=localhost;dbname=your[I]db', 'your[/I]user', 'your_password');

        $stmt = $pdo->prepare("SELECT user[I]id, expires[/I]at, revoked FROM user[I]refresh[/I]tokens WHERE refresh[I]token = ? AND revoked = FALSE AND expires[/I]at > NOW()");
        $stmt->execute([$refreshToken]);
        $tokenData = $stmt->fetch(PDO::FETCH_ASSOC);

        if (!$tokenData) {
            return null; // Refresh token tapılmadı, müddəti bitib və ya ləğv edilib
        }

        $userId = $tokenData['user_id'];

        // Yeni access token yaradın
        $issuedAt = time();
        $expirationTime = $issuedAt + (15 * 60); // Yenə 15 dəqiqə
        $payload = [
            'iat' => $issuedAt,
            'exp' => $expirationTime,
            'sub' => $userId,
        ];
        $newAccessToken = JWT::encode($payload, $this->jwtSecretKey, 'HS256');

        // Köhnə refresh token-i ləğv edin (təkrar istifadənin qarşısını almaq üçün)
        $this->revokeRefreshToken($refreshToken);

        // Yeni refresh token yaradın və onu verilənlər bazasına yazın
        $newRefreshToken = bin2hex(random_bytes(32));
        $newRefreshTokenExpiry = date('Y-m-d H:i:s', strtotime('+7 days'));
        $this->saveRefreshToken($userId, $newRefreshToken

Yanıt vermek için giriş yapmış olmalısınız.

0 alıntı seçildi