OAuth 2.0 и JWT авторизация на Rust: пошаговое руководство для начинающих

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение: зачем нужна авторизация в Rust?

Современные веб-приложения редко обходятся без системы аутентификации и авторизации. Rust, благодаря своей производительности и безопасности памяти, становится популярным выбором для создания высоконагруженных сервисов. Однако работа с протоколами вроде OAuth 2.0 и форматом JWT может показаться сложной для новичков.

В этой статье мы разберем базовые концепции OAuth 2.0 и JWT (JSON Web Token) и реализуем простую систему авторизации на Rust с использованием популярных крейтов. Вы узнаете, как защитить свои API-эндпоинты и работать с токенами доступа.

Что такое OAuth 2.0 и JWT?

Прежде чем писать код, важно понять теорию.

OAuth 2.0: протокол делегированного доступа

OAuth 2.0 — это протокол, который позволяет приложению получить ограниченный доступ к ресурсам пользователя без передачи его логина и пароля. Представьте, что вы хотите войти на сайт через Google или GitHub — это и есть OAuth 2.0.

Основные участники протокола:

  • Resource Owner — пользователь, владеющий данными.
  • Client — ваше приложение на Rust, которое хочет получить доступ.
  • Authorization Server — сервер, который выдает токены (например, Google).
  • Resource Server — API, предоставляющее данные (например, Google Drive).

JWT: компактный формат токена

JWT (JSON Web Token) — это открытый стандарт для создания токенов доступа. Токен состоит из трех частей, разделенных точками:

  • Header — тип токена и алгоритм подписи.
  • Payload — данные (claims), например, ID пользователя или роль.
  • Signature — подпись, которая гарантирует, что токен не был изменен.

Пример JWT: eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.dozjgNrxP0G7n9G4W7N8Y7J8

JWT удобен тем, что не требует хранения сессий на сервере — вся информация уже зашита в токен.

Настройка проекта Rust для авторизации

Мы будем использовать фреймворк Actix Web и несколько вспомогательных крейтов. Создайте новый проект:

cargo new rust-auth-example
cd rust-auth-example

Добавьте зависимости в Cargo.toml:

[dependencies]
actix-web = "4"
actix-rt = "2"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
jsonwebtoken = "9"
chrono = { version = "0.4", features = ["serde"] }
dotenv = "0.15"
env_logger = "0.10"
bcrypt = "0.15"

Структура файлов

Для удобства создадим модульную структуру:

src/
├── main.rs
├── auth.rs
├── models.rs
└── handlers.rs

В .env пропишем секретный ключ для JWT:

JWT_SECRET=supersecretkey12345
DATABASE_URL=sqlite://users.db

Реализация JWT на Rust: от генерации до верификации

Начнем с модуля auth.rs, который будет отвечать за работу с JWT.

Структуры для JWT

// auth.rs
use serde::{Deserialize, Serialize};
use jsonwebtoken::{encode, decode, Header, Validation, EncodingKey, DecodingKey};
use chrono::{Utc, Duration};

#[derive(Debug, Serialize, Deserialize)]
pub struct Claims {
    pub sub: String,       // ID пользователя
    pub exp: usize,        // время истечения
    pub iat: usize,        // время выдачи
}

pub fn create_jwt(user_id: &str, secret: &str) -> Result<String, jsonwebtoken::errors::Error> {
    let now = Utc::now();
    let expiration = now + Duration::hours(24); // токен живет 24 часа

    let claims = Claims {
        sub: user_id.to_owned(),
        exp: expiration.timestamp() as usize,
        iat: now.timestamp() as usize,
    };

    let token = encode(
        &Header::default(),
        &claims,
        &EncodingKey::from_secret(secret.as_bytes()),
    )?;

    Ok(token)
}

pub fn validate_jwt(token: &str, secret: &str) -> Result<Claims, jsonwebtoken::errors::Error> {
    let token_data = decode::<Claims>(
        token,
        &DecodingKey::from_secret(secret.as_bytes()),
        &Validation::default(),
    )?;

    Ok(token_data.claims)
}

Хэширование паролей

Никогда не храните пароли в открытом виде. Используем bcrypt:

// auth.rs (продолжение)
use bcrypt::{hash, verify, DEFAULT_COST};

pub fn hash_password(password: &str) -> Result<String, bcrypt::BcryptError> {
    hash(password, DEFAULT_COST)
}

pub fn verify_password(password: &str, hash: &str) -> Result<bool, bcrypt::BcryptError> {
    verify(password, hash)
}

Создание защищенных API-эндпоинтов на Actix Web

Теперь реализуем хендлеры и middleware для проверки JWT.

Модели данных

// models.rs
use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize)]
pub struct User {
    pub id: String,
    pub username: String,
    pub password_hash: String,
}

#[derive(Debug, Deserialize)]
pub struct LoginRequest {
    pub username: String,
    pub password: String,
}

#[derive(Debug, Serialize)]
pub struct AuthResponse {
    pub token: String,
    pub user_id: String,
}

#[derive(Debug, Serialize)]
pub struct ErrorResponse {
    pub message: String,
}

Middleware для проверки токена

Создадим простой middleware, который будет извлекать JWT из заголовка Authorization:

// handlers.rs
use actix_web::{HttpRequest, HttpResponse, web};
use crate::auth;
use crate::models::*;

pub async fn login(
    body: web::Json<LoginRequest>,
    secret: web::Data<String>,
) -> HttpResponse {
    // В реальном                

Блоги

Рекомендаций по книгам