Rust插件库samael的使用:探索其功能与在Rust生态中的应用

Rust插件库samael的使用:探索其功能与在Rust生态中的应用

Samael

Crates.io MIT licensed

这是一个用于Rust的SAML2库。这是一个正在进行中的项目,欢迎提交Pull Request。

主要功能

  • SAML消息的序列化和反序列化
  • 支持IDP发起的单点登录(SSO)
  • 支持SP发起的SSO Redirect-POST绑定
  • SAML断言验证工具
    • 支持的加密断言:
      • key info:
        • http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p
        • http://www.w3.org/2001/04/xmlenc#rsa-1_5
      • value info:
        • http://www.w3.org/2001/04/xmlenc#aes128-cbc
        • http://www.w3.org/2009/xmlenc11#aes128-gcm
  • SAMLRequest(AuthnRequest)消息签名验证
  • 签名SAMLResponse(Response)消息创建

构建与开发

使用nix确保可重复构建:

nix build
# 或进入开发环境
nix develop

基本使用示例

use samael::metadata::{ContactPerson, ContactType, EntityDescriptor};
use samael::service_provider::ServiceProviderBuilder;
use std::collections::HashMap;
use std::fs;
use warp::Filter;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    openssl_probe::init_ssl_cert_env_vars();

    // 获取IDP元数据
    let resp = reqwest::get("https://samltest.id/saml/idp")
        .await?
        .text()
        .await?;
    let idp_metadata: EntityDescriptor = samael::metadata::de::from_str(&resp)?;

    // 加载公钥和私钥
    let pub_key = openssl::x509::X509::from_pem(&fs::read("./publickey.cer")?)?;
    let private_key = openssl::rsa::Rsa::private_key_from_pem(&fs::read("./privatekey.pem")?)?;

    // 构建服务提供者
    let sp = ServiceProviderBuilder::default()
        .entity_id("".to_string())
        .key(private_key)
        .certificate(pub_key)
        .allow_idp_initiated(true)
        .contact_person(ContactPerson {
            sur_name: Some("Bob".to_string()),
            contact_type: Some(ContactType::Technical.value().to_string()),
            ..ContactPerson::default()
        })
        .idp_metadata(idp_metadata)
        .acs_url("http://localhost:8080/saml/acs".to_string())
        .slo_url("http://localhost:8080/saml/slo".to_string())
        .build()?;

    // 生成元数据
    let metadata = sp.metadata()?.to_string()?;

    // 设置元数据路由
    let metadata_route = warp::get()
        .and(warp::path("metadata"))
        .map(move || metadata.clone());

    // 设置ACS路由
    let acs_route = warp::post()
        .and(warp::path("acs"))
        .and(warp::body::form())
        .map(move |s: HashMap<String, String>| {
            if let Some(encoded_resp) = s.get("SAMLResponse") {
                let t = sp
                    .parse_base64_response(encoded_resp, Some(&["a_possible_request_id"]))
                    .unwrap();
                return format!("{:?}", t);
            }
            format!("")
        });

    // 合并SAML路由
    let saml_routes = warp::path("saml").and(acs_route.or(metadata_route));
    warp::serve(saml_routes).run(([127, 0, 0, 1], 8080)).await;
    Ok(())
}

完整生产级示例

use samael::metadata::{ContactPerson, ContactType, EntityDescriptor};
use samael::service_provider::ServiceProviderBuilder;
use std::collections::HashMap;
use std::fs;
use warp::Filter;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // 初始化SSL证书环境变量
    openssl_probe::init_ssl_cert_env_vars();

    // 1. 获取身份提供者(IDP)元数据
    println!("Fetching IDP metadata...");
    let resp = reqwest::get("https://samltest.id/saml/idp")
        .await?
        .text()
        .await?;
    let idp_metadata: EntityDescriptor = samael::metadata::de::from_str(&resp)?;

    // 2. 加载服务提供者(SP)的公钥和私钥
    println!("Loading SP keys...");
    let pub_key = openssl::x509::X509::from_pem(&fs::read("./publickey.cer")?)?;
    let private_key = openssl::rsa::Rsa::private_key_from_pem(&fs::read("./privatekey.pem")?)?;

    // 3. 配置服务提供者
    println!("Configuring Service Provider...");
    let sp = ServiceProviderBuilder::default()
        .entity_id("http://localhost:8080/metadata".to_string())  // SP实体ID
        .key(private_key)  // SP私钥
        .certificate(pub_key)  // SP公钥证书
        .allow_idp_initiated(true)  // 允许IDP发起的SSO
        .contact_person(ContactPerson {  // 联系人信息
            sur_name: Some("Admin".to_string()),
            email_addresses: Some(vec!["admin@example.com".to_string()]),
            contact_type: Some(ContactType::Technical.value().to_string()),
            ..ContactPerson::default()
        })
        .idp_metadata(idp_metadata)  // IDP元数据
        .acs_url("http://localhost:8080/saml/acs".to_string())  // 断言消费服务URL
        .slo_url("http://localhost:8080/saml/slo".to_string())  // 单点登出URL
        .build()?;

    // 4. 生成SP元数据
    println!("Generating SP metadata...");
    let metadata = sp.metadata()?.to_string()?;

    // 5. 设置元数据路由 (/metadata)
    let metadata_route = warp::get()
        .and(warp::path("metadata"))
        .map(move || metadata.clone());

    // 6. 设置断言消费服务路由 (/saml/acs)
    let acs_route = warp::post()
        .and(warp::path("acs"))
        .and(warp::body::form())
        .map(move |form_data: HashMap<String, String>| {
            if let Some(encoded_resp) = form_data.get("SAMLResponse") {
                println!("Received SAMLResponse");
                
                // 解析和验证SAML响应
                match sp.parse_base64_response(encoded_resp, None) {
                    Ok(response) => {
                        println!("Successfully parsed SAML response");
                        // 这里可以处理成功的认证,例如创建会话
                        format!("Authentication successful! {:?}", response)
                    },
                    Err(e) => {
                        println!("Failed to parse SAML response: {:?}", e);
                        format!("Authentication failed: {:?}", e)
                    }
                }
            } else {
                println!("No SAMLResponse in form data");
                "Invalid SAML response".to_string()
            }
        });

    // 7. 设置登录路由 (/login)
    let login_route = warp::get()
        .and(warp::path("login"))
        .map(move || {
            // 生成SP发起的SSO请求
            match sp.generate_authn_request() {
                Ok((request, redirect_url)) => {
                    println!("Generated AuthnRequest, redirecting to: {}", redirect_url);
                    // 在实际应用中应该重定向到IDP
                    format!("Redirect URL: {}", redirect_url)
                },
                Err(e) => format!("Failed to generate authn request: {:?}", e)
            }
        });

    // 8. 合并所有路由
    let saml_routes = warp::path("saml")
        .and(acs_route.or(metadata_route))
        .or(login_route);

    println!("Starting server at http://localhost:8080");
    warp::serve(saml_routes).run(([127, 0, 0, 1], 8080)).await;
    
    Ok(())
}

安装与授权

在Cargo.toml中添加依赖:

[dependencies]
samael = "0.0.19"

本项目采用MIT许可证。


1 回复

Rust插件库samael的使用:探索其功能与在Rust生态中的应用

简介

samael是一个Rust插件库,旨在为Rust应用程序提供灵活的插件系统支持。它允许开发者动态加载和管理插件,扩展应用程序功能而无需重新编译主程序。

主要功能

  1. 动态插件加载:运行时加载和卸载插件
  2. 跨平台支持:支持Linux、macOS和Windows系统
  3. 类型安全:利用Rust的类型系统确保插件接口安全
  4. 生命周期管理:自动处理插件的初始化和清理
  5. 依赖管理:支持插件间的依赖关系

安装方法

在Cargo.toml中添加依赖:

[dependencies]
samael = "0.3"

基本使用方法

1. 定义插件接口

首先创建一个定义插件接口的库:

// my_plugin_interface/src/lib.rs
pub trait Plugin {
    fn name(&self) -> &str;
    fn execute(&self, input: &str) -> String;
}

#[macro_export]
macro_rules! declare_plugin {
    ($plugin_type:ty, $constructor:path) => {
        #[no_mangle]
        pub extern "C" fn _plugin_create() -> *mut dyn Plugin {
            let constructor: fn() -> $plugin_type = $constructor;
            let object = constructor();
            let boxed: Box<dyn Plugin> = Box::new(object);
            Box::into_raw(boxed)
        }
    };
}

2. 创建插件实现

// my_plugin/Cargo.toml
[lib]
name = "my_plugin"
crate-type = ["cdylib"]

[dependencies]
my_plugin_interface = { path = "../my_plugin_interface" }

// my_plugin/src/lib.rs
use my_plugin_interface::{Plugin, declare_plugin};

struct MyPlugin;

impl Plugin for MyPlugin {
    fn name(&self) -> &str {
        "My First Plugin"
    }

    fn execute(&self, input: &str) -> String {
        format!("Processed: {}", input.to_uppercase())
    }
}

#[no_mangle]
pub fn create() -> MyPlugin {
    MyPlugin
}

declare_plugin!(MyPlugin, create);

3. 主程序加载插件

use samael::{PluginManager, Plugin};
use std::path::Path;

fn main() {
    let mut manager = PluginManager::new();
    
    // 加载插件
    if let Err(e) = manager.load(Path::new("target/debug/libmy_plugin.so")) {
        eprintln!("Failed to load plugin: {}", e);
        return;
    }
    
    // 使用插件
    if let Some(plugin) = manager.get::<dyn Plugin>("My First Plugin") {
        println!("Plugin loaded: {}", plugin.name());
        let result = plugin.execute("hello world");
        println!("Plugin output: {}", result);
    }
    
    // 卸载插件
    manager.unload("My First Plugin");
}

完整示例demo

项目结构

samael_demo/
├── Cargo.toml
├── src/
│   └── main.rs
├── plugins/
│   ├── demo_plugin/
│   │   ├── Cargo.toml
│   │   └── src/
│   │       └── lib.rs
│   └── plugin_interface/
│       ├── Cargo.toml
│       └── src/
│           └── lib.rs

1. 插件接口定义

// plugins/plugin_interface/src/lib.rs
use serde_json::Value;

/// 插件接口定义
pub trait DemoPlugin {
    fn name(&self) -> &str;
    fn version(&self) -> &str;
    fn process(&self, input: &str) -> String;
    fn configure(&mut self, config: Value);  // 添加配置方法
}

/// 插件声明宏
#[macro_export]
macro_rules! declare_demo_plugin {
    ($plugin_type:ty, $constructor:path) => {
        #[no_mangle]
        pub extern "C" fn _plugin_create() -> *mut dyn DemoPlugin {
            let constructor: fn() -> $plugin_type = $constructor;
            let object = constructor();
            let boxed: Box<dyn DemoPlugin> = Box::new(object);
            Box::into_raw(boxed)
        }
    };
}

2. 插件实现

// plugins/demo_plugin/Cargo.toml
[package]
name = "demo_plugin"
version = "0.1.0"
edition = "2021"

[lib]
name = "demo_plugin"
crate-type = ["cdylib"]

[dependencies]
plugin_interface = { path = "../plugin_interface" }
serde_json = "1.0"

// plugins/demo_plugin/src/lib.rs
use plugin_interface::{DemoPlugin, declare_demo_plugin};
use serde_json::Value;

/// 插件实现结构体
pub struct TextProcessor {
    config: Value,
}

impl DemoPlugin for TextProcessor {
    fn name(&self) -> &str {
        "Text Processor Plugin"
    }

    fn version(&self) -> &str {
        "1.0.0"
    }

    fn process(&self, input: &str) -> String {
        // 使用配置中的prefix,如果不存在则使用默认值
        let prefix = self.config.get("prefix")
            .and_then(|v| v.as_str())
            .unwrap_or("PROCESSED");
            
        format!("[{}] {}", prefix, input.to_uppercase())
    }

    fn configure(&mut self, config: Value) {
        self.config = config;
    }
}

/// 插件构造函数
#[no_mangle]
pub fn create_processor() -> TextProcessor {
    TextProcessor {
        config: Value::Null,
    }
}

// 声明插件
declare_demo_plugin!(TextProcessor, create_processor);

3. 主程序

// src/main.rs
use samael::PluginManager;
use std::path::Path;
use plugin_interface::DemoPlugin;
use serde_json::json;

fn main() {
    // 初始化插件管理器
    let mut manager = PluginManager::new();
    
    // 加载插件
    let plugin_path = Path::new("target/debug/libdemo_plugin.so");
    if let Err(e) = manager.load(plugin_path) {
        eprintln!("Failed to load plugin: {}", e);
        return;
    }
    
    // 获取并配置插件
    if let Some(mut plugin) = manager.get_mut::<dyn DemoPlugin>("Text Processor Plugin") {
        println!("Loaded plugin: {} v{}", plugin.name(), plugin.version());
        
        // 配置插件
        let config = json!({
            "prefix": "DEMO"
        });
        plugin.configure(config);
        
        // 使用插件处理数据
        let result = plugin.process("hello samael");
        println!("Processing result: {}", result);
        
        // 再次处理不同数据
        let result2 = plugin.process("another test");
        println!("Second result: {}", result2);
    }
    
    // 卸载插件
    if let Err(e) = manager.unload("Text Processor Plugin") {
        eprintln!("Failed to unload plugin: {}", e);
    }
    
    println!("Plugin unloaded successfully");
}

4. Cargo.toml配置

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

[dependencies]
samael = "0.3"
plugin_interface = { path = "./plugins/plugin_interface" }
serde_json = "1.0"

构建和运行步骤

  1. 首先构建插件接口:
cd plugins/plugin_interface && cargo build
  1. 构建插件:
cd plugins/demo_plugin && cargo build
  1. 运行主程序:
cargo run

预期输出

Loaded plugin: Text Processor Plugin v1.0.0
Processing result: [DEMO] HELLO SAMAEL
Second result: [DEMO] ANOTHER TEST
Plugin unloaded successfully

这个完整示例展示了如何使用samael库创建一个可配置的插件系统,包括插件接口定义、插件实现、主程序加载和配置插件等完整流程。

回到顶部