Skip to content

Storage Backends

中文 | English

Tokens, sessions, and permission caches all land on SaStorage. Version 0.2 ships Memory, Redis, and Database (PostgreSQL) backends. Application code should go through SaTokenDao, not hold raw storage in services.

Plugin features

Enable the matching feature on a framework plugin (for example sa-token-plugin-axum) to re-export the storage type:

FeatureCrateNotes
memory (default)sa-token-storage-memoryIn-process
redissa-token-storage-redisRedis
databasesa-token-storage-databaseRelational KV (default postgres)
fullall of the aboveEnable together
toml
sa-token-plugin-axum = { version = "0.2.0", features = ["redis"] }
# or depend on the storage crate directly
# sa-token-storage-redis = "0.2.0"

Inject into the builder

Wrap every backend as Arc<dyn SaStorage> (or Arc of a concrete type) and pass it to the builder:

rust
use std::sync::Arc;
use sa_token_plugin_axum::*; // or MemoryStorage / RedisStorage / DatabaseStorage

let state = SaTokenState::builder()
    .storage(Arc::new(MemoryStorage::new()))
    .timeout(86400)
    .build();

For libraries, prefer SaTokenConfig::builder().storage(...).try_build()? then StpUtil::try_init_manager.

SaTokenDao

SaTokenManager uses SaTokenDao as the single storage funnel: key layout (SaKeys), serialization, and TTL live there. Repositories and services must not hold SaStorage directly. Most apps only pick a backend and inject it; dig into SaTokenDao when you need custom keys or atomic primitives.


Pluggable serialization (SaSerializer)

Domain objects written to storage (token info, sessions, nonce records, OAuth2/SSO payloads, and so on) go through a pluggable serializer on SaTokenConfig. Call sites should use SharedSerializer (a Clone-friendly enum). The default is JSON; optional binary encoding is available behind the fory feature.

Defaults and when to switch

ChoiceWhen
JSON (default)Compatible with existing 0.1 / early 0.2 data; human-readable in Redis CLI
fory (feature = "fory")Smaller payloads / less Redis string noise; rolling upgrade still reads legacy JSON

You do not need to change anything for a normal install: omit .serializer(...) and keep JSON.

Inject via builder

Types are re-exported from sa-token-core (and from the root sa-token / plugin crates when features allow):

rust
use sa_token_adapter::{JsonSerializer, JsonSerializerConfig};
use sa_token_core::{SaTokenConfig, SharedSerializer};
use sa_token_storage_memory::MemoryStorage;
use std::sync::Arc;

// Default JSON — explicit form
let manager = SaTokenConfig::builder()
    .storage(Arc::new(MemoryStorage::new()))
    .serializer(SharedSerializer::Json(JsonSerializer::default()))
    .try_build()?;

// Pretty JSON for local debugging only (do not use in production)
let debug = SharedSerializer::Json(JsonSerializer::with_config(JsonSerializerConfig {
    pretty_print: true,
    ..Default::default()
}));

Optional fory (binary)

Enable the feature on the crate you depend on:

toml
# root meta-crate
sa-token = { version = "0.2.0", features = ["fory"] }

# or core / adapter directly
sa-token-core = { version = "0.2.0", features = ["fory"] }
rust
#[cfg(feature = "fory")]
use sa_token_core::{ForySerializer, SaTokenConfig, SharedSerializer};
use sa_token_storage_memory::MemoryStorage;
use std::sync::Arc;

#[cfg(feature = "fory")]
let manager = SaTokenConfig::builder()
    .storage(Arc::new(MemoryStorage::new()))
    .serializer(SharedSerializer::from(ForySerializer::default()))
    .try_build()?;

Binary string payloads are prefixed with magic \u{0001}STF (BINARY_MAGIC) so the read path can tell formats apart.

Rolling upgrade semantics

Active serializerReading legacy pure JSONReading magic-prefixed binary
JSONOKFormatMismatch → surfaces as SaTokenError::SerializationError
foryOK (legacy path)OK

Practical rollout: keep writing JSON until all nodes can enable fory, then switch writers; leave fory readers on until old JSON rows expire or are rewritten. Switching back to JSON while binary rows remain will fail decode with a format mismatch — migrate or wait for TTL first.

Errors

SerializerError (EncodeFailed / DecodeFailed / FormatMismatch / VersionIncompatible) maps into SaTokenError::SerializationError(String) via Display. See Error reference.

Trait overview

SaSerializer exposes name / kind / encode / decode, plus optional encode_bytes / decode_bytes. Prefer configuring through SaTokenConfigBuilder::serializer; application code rarely calls the trait directly.


MemoryStorage

Best for development, tests, and single-process non-persistent setups.

rust
use sa_token_storage_memory::MemoryStorage;
use std::sync::Arc;

let storage = Arc::new(MemoryStorage::new());
// optional: sweep expired entries
storage.cleanup_expired().await;

Fast and dependency-free; data is lost on restart and is not shared across processes.


RedisStorage

For production and multi-instance shared sessions. Common constructors:

rust
use sa_token_storage_redis::{RedisStorage, RedisConfig};
use std::sync::Arc;

// 1) URL + key prefix
let storage = RedisStorage::new(
    "redis://:password@localhost:6379/0",
    "sa-token:",
).await?;

// 2) Convenience: empty physical prefix (logical keys come from SaKeys)
let storage = RedisStorage::connect("redis://localhost:6379/0").await?;

// 3) Config struct
let storage = RedisStorage::from_config(
    RedisConfig {
        host: "localhost".into(),
        port: 6379,
        password: Some("password".into()),
        database: 0,
        ..Default::default()
    },
    "sa-token:",
).await?;

let state = SaTokenState::builder()
    .storage(Arc::new(storage))
    .build();

You can also use RedisStorage::builder().host(...).port(...).key_prefix(...).build().await?.

URL examples: redis://localhost:6379/0, redis://:mypass@localhost:6379/0.


DatabaseStorage

sqlx-based PostgreSQL KV storage. The crate’s default feature is postgres:

toml
sa-token-storage-database = "0.2.0"
# equivalent to features = ["postgres"]
rust
use sa_token_storage_database::DatabaseStorage;
use std::sync::Arc;

let storage = DatabaseStorage::new("postgres://user:pass@localhost/db").await?;
// or DatabaseStorage::from_pool(pool)

let state = SaTokenState::builder()
    .storage(Arc::new(storage))
    .build();

new connects and runs the embedded DDL (idempotent). Basic KV (get / set / delete, …) is supported; get_del, CAS, list_*, and scan return StorageError::Unsupported. Use Memory or Redis when you need full atomic/list capabilities.


Capability matrix

CapabilityMemoryRedisDatabase
KV get/set/deleteyesyesyes
get_del / CAS / set_if_absentyesyesunsupported
list_* / scanyesyesunsupported

Custom backends: implement SaStorage from sa-token-adapter and inject with Arc the same way.

MIT OR Apache-2.0