OAuth 2.0 и JWT авторизация на Rust: полное руководство

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

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

Начать курс

Введение в OAuth 2.0 и JWT на Rust

Современные веб-приложения требуют надёжной и безопасной аутентификации. Два ключевых стандарта в этой области — OAuth 2.0 (протокол авторизации) и JWT (JSON Web Token, формат токена доступа). Rust, благодаря своей производительности и безопасности памяти, становится отличным выбором для построения API, где критически важны скорость и надёжность.

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

Основные концепции OAuth 2.0 и JWT

Что такое OAuth 2.0?

OAuth 2.0 — это протокол делегированного доступа. Он позволяет приложению получать ограниченный доступ к ресурсам пользователя без передачи его учётных данных (логина и пароля). Основные участники:

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

Что такое JWT?

JWT — это компактный, URL-безопасный формат токена, состоящий из трёх частей: заголовка (header), полезной нагрузки (payload) и подписи (signature). Подпись гарантирует целостность данных. JWT часто используется как токен доступа (access token) в OAuth 2.0, так как он самодостаточен: сервер может проверить его без обращения к базе данных.

Практическая реализация на Rust

Для реализации нам понадобятся следующие крейты:

  • actix-web — веб-фреймворк.
  • serde и serde_json — для сериализации/десериализации.
  • jsonwebtoken — для работы с JWT.
  • dotenv — для управления конфигурацией.
  • chrono — для работы с датами и временем.

Шаг 1: Настройка проекта и структуры

Создайте новый проект Cargo и добавьте зависимости в Cargo.toml:

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

Создайте файл .env для хранения секретного ключа:

JWT_SECRET=my_super_secret_key_12345

Шаг 2: Создание модели данных и JWT-функций

Определим структуры для JWT и реализуем функции создания и проверки токенов.

use chrono::{Utc, Duration};
use jsonwebtoken::{encode, decode, Header, Validation, EncodingKey, DecodingKey};
use serde::{Deserialize, Serialize};
use std::env;

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

pub fn create_jwt(user_id: &str) -> Result<String, jsonwebtoken::errors::Error> {
    let secret = env::var("JWT_SECRET").expect("JWT_SECRET must be set");
    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) -> Result<Claims, jsonwebtoken::errors::Error> {
    let secret = env::var("JWT_SECRET").expect("JWT_SECRET must be set");
    let token_data = decode::<Claims>(
        token,
        &DecodingKey::from_secret(secret.as_bytes()),
        &Validation::default(),
    )?;
    Ok(token_data.claims)
}

Шаг 3: Реализация эндпоинтов OAuth 2.0 (симуляция)

В реальном OAuth 2.0 клиент перенаправляется на сервер авторизации. Мы упростим процесс и создадим эндпоинт для входа, который возвращает JWT.

use actix_web::{web, HttpRequest, HttpResponse, Responder};
use serde::Deserialize;

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

pub async fn login(body: web::Json<LoginRequest>) -> impl Responder {
    // В реальном приложении здесь проверка пароля в БД
    if body.username == "admin" && body.password == "password" {
        match create_jwt(&body.username) {
            Ok(token) => HttpResponse::Ok().json(serde_json::json!({"access_token": token, "token_type": "Bearer"})),
            Err(_) => HttpResponse::InternalServerError().json(serde_json::json!({"error": "Token creation failed"})),
        }
    } else {
        HttpResponse::Unauthorized().json(serde_json::json!({"error": "Invalid credentials"}))
    }
}

pub async fn protected_resource(req: HttpRequest) -> impl Responder {
    // Извлекаем токен из заголовка Authorization
    let auth_header = req.headers().get("Authorization");
    if let Some(header_value) = auth_header {
        let token_str = header_value.to_str().unwrap_or("");
        if token_str.starts_with("Bearer ") {
            let token = &token_str[7..];
            match validate_jwt(token) {
                Ok(claims) => HttpResponse::Ok().json(serde_json::json!({
                    "message": "Access granted",
                    "user_id": claims.sub,
                    "exp": claims.exp
                })),
                Err(_) => HttpResponse::Unauthorized().json(serde_json::json!({"error": "Invalid token"})),
            }
        } else {
            HttpResponse::BadRequest().json(serde_json::json!({"error": "Invalid Authorization header format"}))
        }
    } else {
        HttpResponse::Una                

Блоги

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