You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Rust + Apache Flight:如何绕过库中&self定义的trait方法限制

针对Apache Arrow Flight Rust服务认证的解决方案

1. 用内部可变性绕过不可引用限制

因为FlightService要求'static生命周期且handshake方法使用&self,可以通过Rust的内部可变性机制,在不可变引用内部修改状态。多线程服务下使用线程安全的容器(如tokio::sync::Mutex或std::sync::Arc<RwLock>),单线程场景可用RefCell。

示例实现:

use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::Mutex;
use arrow_flight::flight_service_server::FlightService;
use chrono::{Utc, Duration};

// 存储令牌元数据
#[derive(Debug)]
struct TokenMetadata {
    user_id: String,
    expires_at: chrono::DateTime<Utc>,
}

struct AuthFlightService {
    active_tokens: Arc<Mutex<HashMap<String, TokenMetadata>>>,
}

// 满足'static要求,因为Arc和Mutex都是'static类型
impl FlightService for AuthFlightService {
    type HandshakeStream = ...;

    async fn handshake(
        &self,
        request: tonic::Request<tonic::Streaming<arrow_flight::HandshakeRequest>>,
    ) -> Result<tonic::Response<Self::HandshakeStream>, tonic::Status> {
        // 从请求中提取用户名密码等认证信息
        let auth_req = extract_auth_request(request).await?;
        if !validate_credentials(&auth_req.username, &auth_req.password) {
            return Err(tonic::Status::unauthenticated("Invalid credentials"));
        }

        // 生成唯一随机令牌
        let token = generate_unique_token();
        let metadata = TokenMetadata {
            user_id: auth_req.username,
            expires_at: Utc::now() + Duration::hours(1),
        };

        // 修改内部令牌存储
        let mut tokens = self.active_tokens.lock().await.map_err(|e| {
            tonic::Status::internal(format!("Failed to access token store: {}", e))
        })?;
        tokens.insert(token.clone(), metadata);

        // 返回Bearer令牌给客户端
        let response = arrow_flight::HandshakeResponse {
            payload: Some(arrow_flight::handshake_response::Payload::AuthBearer(token)),
            ..Default::default()
        };
        // 处理流响应逻辑...
        Ok(tonic::Response::new(...))
    }

    // 其他方法中验证令牌
    async fn get_flight_info(
        &self,
        request: tonic::Request<arrow_flight::FlightDescriptor>,
    ) -> Result<tonic::Response<arrow_flight::FlightInfo>, tonic::Status> {
        let token = extract_bearer_token(&request).ok_or_else(|| {
            tonic::Status::unauthenticated("No bearer token provided")
        })?;

        let tokens = self.active_tokens.lock().await.map_err(|e| {
            tonic::Status::internal(format!("Failed to access token store: {}", e))
        })?;

        match tokens.get(&token) {
            Some(meta) if meta.expires_at > Utc::now() => {
                // 认证通过,处理请求
                Ok(tonic::Response::new(...))
            }
            Some(_) => Err(tonic::Status::unauthenticated("Token expired")),
            None => Err(tonic::Status::unauthenticated("Invalid token")),
        }
    }
}

注意:异步场景优先使用tokio::sync::Mutex,避免阻塞整个执行器。

2. 使用无状态加密令牌(JWT)

如果不想维护服务器端令牌存储,JWT是更简洁的方案。JWT本身包含用户信息和过期时间,通过服务器密钥签名,验证时无需查询存储,完全无状态。

示例实现(依赖jsonwebtoken和serde crate):

use jsonwebtoken::{encode, decode, Header, Validation, EncodingKey, DecodingKey};
use serde::{Serialize, Deserialize};
use arrow_flight::flight_service_server::FlightService;
use chrono::{Utc, Duration};

#[derive(Debug, Serialize, Deserialize)]
struct JwtClaims {
    sub: String, // 用户ID
    exp: usize,  // 过期时间(Unix时间戳)
}

struct JwtFlightService {
    secret_key: Vec<u8>,
}

impl FlightService for JwtFlightService {
    type HandshakeStream = ...;

    async fn handshake(
        &self,
        request: tonic::Request<tonic::Streaming<arrow_flight::HandshakeRequest>>,
    ) -> Result<tonic::Response<Self::HandshakeStream>, tonic::Status> {
        let auth_req = extract_auth_request(request).await?;
        if !validate_credentials(&auth_req.username, &auth_req.password) {
            return Err(tonic::Status::unauthenticated("Invalid credentials"));
        }

        // 生成JWT声明
        let exp = Utc::now() + Duration::hours(1);
        let claims = JwtClaims {
            sub: auth_req.username,
            exp: exp.timestamp() as usize,
        };

        // 签发JWT
        let token = encode(
            &Header::default(),
            &claims,
            &EncodingKey::from_secret(&self.secret_key),
        ).map_err(|e| {
            tonic::Status::internal(format!("Failed to generate token: {}", e))
        })?;

        // 返回令牌
        let response = arrow_flight::HandshakeResponse {
            payload: Some(arrow_flight::handshake_response::Payload::AuthBearer(token)),
            ..Default::default()
        };
        Ok(tonic::Response::new(...))
    }

    async fn get_flight_info(
        &self,
        request: tonic::Request<arrow_flight::FlightDescriptor>,
    ) -> Result<tonic::Response<arrow_flight::FlightInfo>, tonic::Status> {
        let token = extract_bearer_token(&request).ok_or_else(|| {
            tonic::Status::unauthenticated("No bearer token provided")
        })?;

        // 验证JWT
        let decoded = decode::<JwtClaims>(
            &token,
            &DecodingKey::from_secret(&self.secret_key),
            &Validation::default(),
        ).map_err(|e| {
            tonic::Status::unauthenticated(format!("Invalid token: {}", e))
        })?;

        // 可选:二次检查过期时间(Validation默认已处理)
        if decoded.claims.exp < Utc::now().timestamp() as usize {
            return Err(tonic::Status::unauthenticated("Token expired"));
        }

        // 认证通过,处理请求
        Ok(tonic::Response::new(...))
    }
}

JWT的优势是无状态、易扩展,适合分布式部署;缺点是无法主动撤销已签发的令牌,除非配合黑名单或缩短过期时间+刷新令牌机制。

方案选择

  • 需要主动撤销令牌或令牌内容敏感:选择内部可变性+服务器存储方案
  • 追求无状态、低维护成本:选择JWT方案

内容的提问来源于stack exchange,提问作者jwimberley

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.21 16:22:16