1pub mod config;
57pub mod configurator;
58#[cfg(feature = "with-spring-redis")]
59pub mod storage;
60
61use crate::config::SaTokenConfig;
62use sa_token_adapter::SaStorage;
63use spring::app::AppBuilder;
64use spring::async_trait;
65use spring::config::ConfigRegistry;
66use spring::plugin::ComponentRegistry;
67use spring::plugin::{MutableComponentRegistry, Plugin};
68#[cfg(feature = "with-web")]
69use spring_web::LayerConfigurator;
70use std::sync::Arc;
71
72pub use sa_token_adapter;
78pub use sa_token_core;
79pub use sa_token_plugin_axum;
80
81pub use crate::config::{CoreConfig, TokenStyle};
83
84pub use sa_token_core::{SaTokenManager, StpUtil};
86pub use sa_token_plugin_axum::{LoginIdExtractor, OptionalSaTokenExtractor, SaTokenExtractor};
87pub use sa_token_plugin_axum::{SaTokenLayer, SaTokenMiddleware, SaTokenState};
88
89pub use spring_macros::sa_check_login;
91pub use spring_macros::sa_check_permission;
92pub use spring_macros::sa_check_permissions_and;
93pub use spring_macros::sa_check_permissions_or;
94pub use spring_macros::sa_check_role;
95pub use spring_macros::sa_check_roles_and;
96pub use spring_macros::sa_check_roles_or;
97pub use spring_macros::sa_ignore;
98
99#[cfg(feature = "memory")]
101pub use sa_token_storage_memory::MemoryStorage;
102
103pub use crate::configurator::{PathAuthBuilder, SaTokenAuthConfigurator, SaTokenConfigurator};
105
106pub use sa_token_core::error::SaTokenError;
108use sa_token_core::PathAuthConfig;
109
110pub struct SaTokenPlugin;
115
116#[async_trait]
117impl Plugin for SaTokenPlugin {
118 async fn build(&self, app: &mut AppBuilder) {
119 let config = app
120 .get_config::<SaTokenConfig>()
121 .expect("sa-token plugin config load failed");
122
123 tracing::info!("Initializing Sa-Token plugin...");
124
125 let state = Self::create_state(app, config)
126 .await
127 .expect("sa-token state creation failed");
128
129 tracing::debug!(
130 "SaTokenState manager.config.token_name = {}",
131 state.manager.config.token_name
132 );
133
134 tracing::info!("Sa-Token plugin initialized successfully");
135
136 app.add_component(state.clone());
138
139 #[cfg(feature = "with-web")]
142 {
143 if let Some(config) = app.get_component::<PathAuthConfig>() {
145 tracing::info!("Registering SaTokenLayer with path-based authentication");
146 app.add_router_layer(move |router| {
147 router.layer(SaTokenLayer::with_path_auth(state.clone(), config.clone()))
148 });
149 } else {
150 tracing::info!("Registering SaTokenLayer as router middleware (no path config)");
151 app.add_router_layer(move |router| router.layer(SaTokenLayer::new(state.clone())));
152 }
153 }
154 }
155
156 fn name(&self) -> &str {
157 "spring_sa_token::SaTokenPlugin"
158 }
159
160 #[cfg(feature = "with-spring-redis")]
161 fn dependencies(&self) -> Vec<&str> {
162 vec!["spring_redis::RedisPlugin"]
163 }
164}
165
166impl SaTokenPlugin {
167 async fn create_state(app: &AppBuilder, config: SaTokenConfig) -> anyhow::Result<SaTokenState> {
175 let storage = Self::configure_storage(app, &config).await?;
177
178 tracing::debug!(
179 "Sa-Token config: token_name={}, timeout={}, auto_renew={}, is_concurrent={}, is_share={}",
180 config.token_name, config.timeout, config.auto_renew, config.is_concurrent, config.is_share
181 );
182
183 let mut builder = sa_token_core::SaTokenConfig::builder()
186 .storage(storage)
187 .token_name(config.token_name)
188 .timeout(config.timeout)
189 .active_timeout(config.active_timeout)
190 .auto_renew(config.auto_renew)
191 .is_concurrent(config.is_concurrent)
192 .is_share(config.is_share)
193 .token_style(config.token_style.into())
194 .enable_nonce(config.enable_nonce)
195 .nonce_timeout(config.nonce_timeout)
196 .enable_refresh_token(config.enable_refresh_token)
197 .refresh_token_timeout(config.refresh_token_timeout);
198
199 if let Some(prefix) = config.token_prefix {
201 builder = builder.token_prefix(prefix);
202 }
203 if let Some(key) = config.jwt_secret_key {
204 builder = builder.jwt_secret_key(key);
205 }
206 if let Some(algorithm) = config.jwt_algorithm {
207 builder = builder.jwt_algorithm(algorithm);
208 }
209 if let Some(issuer) = config.jwt_issuer {
210 builder = builder.jwt_issuer(issuer);
211 }
212 if let Some(audience) = config.jwt_audience {
213 builder = builder.jwt_audience(audience);
214 }
215
216 let manager = builder.build();
218
219 Ok(SaTokenState::from_manager(manager))
221 }
222
223 #[allow(unused_variables)]
231 async fn configure_storage(
232 app: &AppBuilder,
233 config: &SaTokenConfig,
234 ) -> anyhow::Result<Arc<dyn SaStorage>> {
235 #[cfg(feature = "with-spring-redis")]
237 {
238 if let Some(redis) = app.get_component::<spring_redis::Redis>() {
239 tracing::info!("Using SpringRedisStorage (reusing spring-redis connection)");
240 let storage = storage::SpringRedisStorage::new(redis);
241 Ok(Arc::new(storage))
242 } else {
243 anyhow::bail!(
244 "Feature 'with-spring-redis' is enabled but RedisPlugin is not added. \
245 Please add RedisPlugin before SaTokenPlugin."
246 );
247 }
248 }
249
250 #[cfg(all(feature = "memory", not(feature = "with-spring-redis")))]
252 {
253 tracing::info!("Using Memory storage");
254 Ok(Arc::new(MemoryStorage::new()))
255 }
256
257 #[cfg(not(any(feature = "memory", feature = "with-spring-redis")))]
259 {
260 anyhow::bail!(
261 "No storage backend available. Enable 'memory' or 'with-spring-redis' feature."
262 );
263 }
264 }
265}