WEB3DEV

Cover image for Rust Story: Gerador de Token de Autenticação Simples em Rust Usando Tokio
Isabela Curado Nehme
Isabela Curado Nehme

Posted on

Rust Story: Gerador de Token de Autenticação Simples em Rust Usando Tokio

No mundo digital, onde a segurança é fundamental e o tempo é essencial, o gerenciamento eficaz de tokens de acesso torna-se tanto uma arte quanto uma ciência. Imagine um mundo onde cada aperto de mão digital seja governado por uma senha com limite de tempo, um token que contém a chave para uma comunicação segura e autorizada. Esta é uma história sobre esses tokens, sua intrincada dança com o tempo e as engrenagens através do Rust que mantêm essa dança em perfeita harmonia.

Nossa história se desenrola no cenário servidor-cliente, um lugar onde a confiança é fundamental e cada interação é examinada. Aqui, um servidor deve enviar uma resposta ao cliente com um charme especial – um token de autenticação. Mas esse token não é uma chave comum; é semelhante à carruagem da Cinderela, mágica, mas limitada pelo tempo. À medida que o relógio avança e atinge a marca de 20 minutos, o token, assim como a carruagem, se transforma, garantindo que nenhum token expirado ultrapasse o prazo de boas-vindas.

Nessa dança de tokens, recorremos ao Rust, a linguagem conhecida pela sua robustez e confiabilidade, para coreografar os nossos passos. Nossas ferramentas? Tokio para criar tarefas que vigiam o relógio, e Axum para construir um servidor, o palco para nossa atuação.

Imagine um novo projeto de carga tendo token_manager como bastidor, onde a magia do código se transforma em performance. Aqui, dependências como Axum e Tokio são os ajudantes de palco, montando o cenário para a nossa peça.

Nosso roteiro está escrito em routes.rs, onde AuthToken e TokenManager assumem os papéis principais. AuthToken é um personagem meticuloso, sempre atento ao tique-taque do relógio, enquanto TokenManager é o mentor, garantindo que os tokens estejam atualizados e que o desempenho seja perfeito.

À medida que nossa história avança, TokenManager ganha vida, fazendo malabarismos com AuthTokens com precisão. É um espetáculo de tarefas assíncronas, onde tokens são gerados, monitorados e atualizados – tudo em uma dança rítmica orquestrada pelos ticks do Tokio.

Nosso palco está montado com lazy_static!, garantindo que nosso TokenManager esteja sempre pronto, sempre alerta. O tempo de expiração padrão e a contagem de ticks são como o andamento da nossa música, orientando o ritmo da nossa apresentação.

No grande final, nosso main.rs, a cortina sobe em um servidor Axum. As rotas são expostas como caminhos em nosso palco, cada uma levando a um ato diferente de nossa história simbólica. Como público, você pode testemunhar a geração do token, uma maravilha por si só, atualizada a cada 5 segundos, uma prova da precisão do nosso código.

Ao navegar para http://localhost:3000, você não está apenas acessando uma rota; está entrando em um mundo onde os tokens dançam ao som do Rust, onde a segurança é uma performance e onde a expiração de cada token é uma deixa para o próximo entrar em cena.

Neste mundo, o another_route é a sua janela para o espetáculo em andamento, uma espiada por trás das cortinas do nosso teatro de tokens. Aqui, você vê o token ativo, uma estrela por si só, desempenhando seu papel perfeitamente até a hora de passar o bastão.

E assim, nossa história de gerenciamento de tokens em Rust termina, mas o espetáculo nunca termina de verdade. É um ciclo, um loop contínuo de segurança e eficiência, um balé de bytes e tokens, todos desempenhando seu papel no vasto teatro digital. Bem-vindo ao mundo do gerenciamento de tokens em Rust – seguro, eficiente e sempre pontual.

Vamos criar um novo projeto de carga token_manager e atualizar o toml da seguinte forma:

[package]
name = "token_manager"
version = "0.1.0"
edition = "2021"

# Veja mais chaves e suas definições em https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
axum = "0.6.20"
hyper = { version = "0.14.27", features = ["full"] }
chrono = {version = "0.4.31" }

tokio = { version = "1.34.0", features = ["full"] }
lazy_static = "1.4.0"
Enter fullscreen mode Exit fullscreen mode

Crie um crate route.rs que terá uma struct de token real e uma struct token_manager para expor APIs de monitoramento de token da seguinte maneira:

//routes.rs
use chrono::{DateTime, Utc};
use std::sync::{Arc, RwLock};
use tokio::time::{interval, Duration};

lazy_static! {
    pub static ref GLOBAL_TOKEN_MANAGER: TokenManager = TokenManager::new(5, 1);
}

struct AuthToken {
    token: String,
    exp_time: DateTime<Utc>,
    expire_seconds: i64,
}

impl AuthToken {
    pub fn new(expire_seconds: i64) -> Self {
        AuthToken {
            token: String::new(),
            exp_time: Utc::now() + chrono::Duration::seconds(expire_seconds),
            expire_seconds,
        }
    }

    pub async fn update_token(&mut self) {
        println!("Generating new token!");
        self.token = AuthToken::generate_secure_token().await;
        self.exp_time = Utc::now() + chrono::Duration::seconds(self.expire_seconds);
    }

    pub fn refresh_token(&mut self, token: &str) {
        println!("Generating new token!");
        self.token = token.to_owned();
        self.exp_time = Utc::now() + chrono::Duration::seconds(self.expire_seconds);
    }

    pub fn is_expired(&self) -> bool {
        Utc::now() > self.exp_time
    }

    pub fn get_token(&self) -> String {
        self.token.clone()
    }

    pub async fn generate_secure_token() -> String {
        let mut interval = interval(Duration::from_secs(1));
        interval.tick().await;
        Utc::now().to_string()
    }
 }

 pub struct TokenManager {
    auth_token: Arc<RwLock<AuthToken>>,
    tick_interval_secs: u64,
 }

 impl TokenManager {
    pub fn new(expire_seconds: i64, tick_interval_secs: u64) -> Self {
        Self {
            auth_token: Arc::new(RwLock::new(AuthToken::new(expire_seconds))),
            tick_interval_secs,
        }
    }

    pub fn fetch_token(&self) -> Option<String> {
        let token = self.auth_token.clone();
        let token_string = match token.read() {
            Ok(t) => Some(t.get_token()),
            Err(e) => {
                eprintln!("Failed to acquire lock: {}", e);
                None
            }
        };
        token_string
    }

    pub async fn refresh_token(&self) {
        let token_ref = self.auth_token.clone();
        let new_token = AuthToken::generate_secure_token().await;
        match token_ref.write() {
            Ok(mut t) => t.refresh_token(&new_token),
            Err(e) => {
                eprintln!("Failed to acquire lock: {}", e);
            }
        };
    }

    pub async fn generate_tokens(&self) -> tokio::task::JoinHandle<()> {
        let token_ref = self.auth_token.clone();
        let interval_secs = self.tick_interval_secs;
        tokio::spawn(async move {
            let mut interval = interval(Duration::from_secs(interval_secs));
            loop {
                interval.tick().await;

                // Verifica se o token precisa ser atualizado
                let needs_refresh = {
                    let token = match token_ref.read() {
                        Ok(t) => t,
                        Err(e) => {
                            eprintln!("Failed to acquire lock: {}", e);
                            continue;
                        }
                    };
                    token.is_expired()
                };

                // Atualiza o token, se necessário
                if needs_refresh {
                    let new_token = AuthToken::generate_secure_token().await;
                    let mut token = match token_ref.write() {
                        Ok(t) => t,
                        Err(e) => {
                            eprintln!("Failed to acquire lock: {}", e);
                            continue;
                        }
                    };
                    token.refresh_token(&new_token);
                }
            }
        })
    }
}
Enter fullscreen mode Exit fullscreen mode

A struct AuthToken tem os seguintes atributos:

struct AuthToken {
token: String, // isto conterá o token real
exp_time: DateTime<Utc>, // isto manterá o tempo de expiração do token ativo atual
expire_seconds: i64,// isto manterá as informações do tempo de expiração de um token. 
}
Enter fullscreen mode Exit fullscreen mode

Agora crie um TokenManager, que conterá AuthToken RwLock e contagem de ticks para esperar antes de verificar o tempo de expiração do token para um token ativo:

pub struct TokenManager {
    auth_token: Arc<RwLock<AuthToken>>, // objeto real da struct do token
    tick_interval_secs: u64,// período de espera para verificar o tempo de expiração de um token ativo
}
Enter fullscreen mode Exit fullscreen mode

TokenManager terá um método público auxiliar para gerar e buscar o token sempre que necessário. É importante entender como o método generate_token funciona com base em nossos requisitos.

generate_token gera uma tarefa assíncrona do Tokio e faz uma chamada de leitura ao objeto auth_token para verificar se o token expirou ou não. Se o token tiver expirado, ele retornará true para o sinalizador need_refresh e, uma vez que o sinalizador need_refresh for definido como true, um bloqueio de escrita no auth_token é obtido. Um novo token será gerado antes de chamar o método refresh_token.

pub async fn generate_tokens(&self) -> tokio::task::JoinHandle<()> {
        let token_ref = self.auth_token.clone();
        let interval_secs = self.tick_interval_secs;
        tokio::spawn(async move {
            let mut interval = interval(Duration::from_secs(interval_secs));
            loop {
                interval.tick().await;

                // Verifica se o token precisa ser atualizado
                let needs_refresh = {
                    let token = match token_ref.read() {
                        Ok(t) => t,
                        Err(e) => {
                            eprintln!("Failed to acquire lock: {}", e);
                            continue;
                        }
                    };
                    token.is_expired()
                };

                // Atualiza o token, se necessário
                if needs_refresh {
                    let new_token = AuthToken::generate_secure_token().await;
                    let mut token = match token_ref.write() {
                        Ok(t) => t,
                        Err(e) => {
                            eprintln!("Failed to acquire lock: {}", e);
                            continue;
                        }
                    };
                    token.refresh_token(&new_token);
                }
            }
        })
    }
}
Enter fullscreen mode Exit fullscreen mode

Vamos criar uma variável estática para inicializar e acessar o TokenManger globalmente:

lazy_static! {
    pub static ref GLOBAL_TOKEN_MANAGER: TokenManager = TokenManager::new(5, 1);
}
Enter fullscreen mode Exit fullscreen mode

Podemos definir expiration_time e tick_count padrão.

Agora, vamos criar uma função principal para demonstrar o requisito:

//main.rs

use axum::{routing::get, Router};

// usar axum_server::Server;
#[macro_use]
extern crate lazy_static;

mod routes;
use crate::routes::route;

async fn token_logic() -> tokio::task::JoinHandle<()> {
    route::GLOBAL_TOKEN_MANAGER.refresh_token().await;
    let handle = route::GLOBAL_TOKEN_MANAGER.generate_tokens().await;

    handle
}

pub async fn another_route() -> String {
    if let Some(token) = route::GLOBAL_TOKEN_MANAGER.fetch_token() {
        String::from(token)
    } else {
        String::new()
    }
    // "Esta é outra rota."
}

#[tokio::main]
async fn main() {
    // construir nossa aplicação com uma única rota
    let app = Router::new()
        .route("/", get(|| async { "Hello, World!" }))
        .route("/another", axum::routing::get(another_route));

    let handle = token_logic().await;

    // execute-o com hyper em localhost:3000
    axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
        .serve(app.into_make_service())
        .await
        .unwrap();
    // handle.abort();
    // println!("token aborted");
}
Enter fullscreen mode Exit fullscreen mode

Em main, estamos criando uma aplicação que irá gerar um token e iniciar o servidor Axum com rotas expostas. Se você executar http://localhost:3000 em seu navegador, verá que a geração de token foi iniciada e ele está atualizando o token a cada 5 segundos, depois de verificar o tempo de expiração do token gerado continuamente a cada 1 segundo.

Você vê o valor do token ativo acessando a rota “another_route” (outra rota) em http://localhost:3000 /another_route.

Você pode atualizar a lógica generate_secure_token para criar token com base em sua necessidade.

É assim que podemos criar uma aplicação de token de acesso em Rust!

Este artigo foi escrito por Shobhit Chaturvedi e traduzido por Isabela Curado Nehme. Seu original pode ser lido aqui.

Top comments (0)