Rust插件库samael的使用:探索其功能与在Rust生态中的应用
Rust插件库samael的使用:探索其功能与在Rust生态中的应用
Samael
这是一个用于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
- key info:
- 支持的加密断言:
- 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应用程序提供灵活的插件系统支持。它允许开发者动态加载和管理插件,扩展应用程序功能而无需重新编译主程序。
主要功能
- 动态插件加载:运行时加载和卸载插件
- 跨平台支持:支持Linux、macOS和Windows系统
- 类型安全:利用Rust的类型系统确保插件接口安全
- 生命周期管理:自动处理插件的初始化和清理
- 依赖管理:支持插件间的依赖关系
安装方法
在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"
构建和运行步骤
- 首先构建插件接口:
cd plugins/plugin_interface && cargo build
- 构建插件:
cd plugins/demo_plugin && cargo build
- 运行主程序:
cargo run
预期输出
Loaded plugin: Text Processor Plugin v1.0.0
Processing result: [DEMO] HELLO SAMAEL
Second result: [DEMO] ANOTHER TEST
Plugin unloaded successfully
这个完整示例展示了如何使用samael库创建一个可配置的插件系统,包括插件接口定义、插件实现、主程序加载和配置插件等完整流程。