Введение: зачем нужна авторизация в 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 {
// В реальном