https://github.com/fireflyframework/fireflyframework-security-adapter-r2dbc
Firefly Framework — R2DBC security data adapter: token revocation list over PostgreSQL (real Postgres Testcontainer IT)
https://github.com/fireflyframework/fireflyframework-security-adapter-r2dbc
firefly-framework hexagonal-architecture java microservices postgresql r2dbc reactive security spring-boot spring-security token-revocation
Last synced: 21 days ago
JSON representation
Firefly Framework — R2DBC security data adapter: token revocation list over PostgreSQL (real Postgres Testcontainer IT)
- Host: GitHub
- URL: https://github.com/fireflyframework/fireflyframework-security-adapter-r2dbc
- Owner: fireflyframework
- License: apache-2.0
- Created: 2026-06-18T19:48:02.000Z (about 1 month ago)
- Default Branch: main
- Last Pushed: 2026-06-26T10:57:32.000Z (27 days ago)
- Last Synced: 2026-06-26T12:09:36.403Z (27 days ago)
- Topics: firefly-framework, hexagonal-architecture, java, microservices, postgresql, r2dbc, reactive, security, spring-boot, spring-security, token-revocation
- Language: Java
- Size: 15.6 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# Firefly Framework - Security R2DBC Adapter
[](LICENSE)
[](https://openjdk.org)
[](https://spring.io/projects/spring-boot)
> R2DBC/relational-database adapter for the Firefly security platform. Implements the `RevocationPort` driven SPI on top of any R2DBC-supported relational database (tested against PostgreSQL), so token revocation works with **no external cache server required**. This is the data-repository alternative to the Redis-backed adapter.
---
## Table of Contents
- [Overview](#overview)
- [Where it sits](#where-it-sits-in-the-hexagonal-security-platform)
- [What it provides](#what-it-provides)
- [Schema](#schema)
- [Requirements](#requirements)
- [Installation](#installation)
- [Usage](#usage)
- [Dependencies](#dependencies)
- [Testing](#testing)
- [Documentation](#documentation)
- [License](#license)
## Overview
The Firefly security platform is a **product-agnostic, hexagonal** security stack built on Spring Security 6 and Spring WebFlux. Its core never imports a vendor SDK; outbound capabilities are expressed as driven ports in `fireflyframework-security-spi` and satisfied by interchangeable adapters.
This module is one such adapter. It implements **`RevocationPort`** — the seam that lets the platform reject a still-unexpired, validly-signed token (a logout, a forced session kill, a compromised credential) — using a relational table reached over R2DBC. It is the **data-repository alternative** to `fireflyframework-security-adapter-redis`: when an application already runs a relational database and does not want to operate a separate cache server, it can pick this adapter and get the same fail-closed revocation semantics from durable storage.
The adapter holds no domain concepts. It stores only an opaque `token_id` (for a JWT, typically the `jti`) and an optional expiry. Nothing here is tied to any particular product, tenant model, or business role.
## Where it sits in the hexagonal security platform
```
api -> spi -> core -> webflux -> resource-server -> adapters
```
| Layer | Module | Role |
| --- | --- | --- |
| `api` | `fireflyframework-security-api` | Driving ports + neutral domain (`SecurityPrincipal`, decisions, …). |
| `spi` | `fireflyframework-security-spi` | Driven ports, including **`RevocationPort`**. |
| `core` | `fireflyframework-security-core` | Vendor-neutral engine; depends on ports, never on adapters. |
| `webflux` | `fireflyframework-security-webflux` | Reactive binding of the core to Spring WebFlux. |
| `resource-server` | `fireflyframework-security-resource-server` | Bearer validation, revocation check, secure-by-default headers/CORS/CSRF. |
| `adapters` | **`fireflyframework-security-adapter-r2dbc`** (this module), `-adapter-redis`, … | Concrete `RevocationPort` (and sibling-port) implementations. |
The resource server consults `RevocationPort.isRevoked(...)` while validating an inbound bearer token and **fails closed**: a revoked (or unverifiable) token is rejected. This adapter depends only on `fireflyframework-security-api` and `fireflyframework-security-spi` — it never reaches back into the core or webflux layers, preserving the hexagonal dependency direction.
## What it provides
### `R2dbcRevocationAdapter` (implements `org.fireflyframework.security.spi.RevocationPort`)
A reactive, R2DBC-backed revocation list. Constructed with a single Spring `DatabaseClient`:
```java
public R2dbcRevocationAdapter(DatabaseClient databaseClient)
```
It implements the two `RevocationPort` operations:
| Method | Behavior |
| --- | --- |
| `Mono revoke(String tokenId, Instant expiresAt)` | **Idempotent upsert** into `security_revoked_token` (`INSERT ... ON CONFLICT (token_id) DO UPDATE`). `expiresAt` may be `null` (bound as a SQL `NULL`), meaning the entry never self-expires; otherwise it is stored as a `TIMESTAMPTZ` at UTC. |
| `Mono isRevoked(String tokenId)` | Returns `true` only for an entry that exists **and is still active** (`expires_at IS NULL OR expires_at > now()`). An unknown token returns `false` via `defaultIfEmpty(false)`. |
**Self-expiring entries.** Because `isRevoked` ignores rows whose `expires_at` has passed, an expired revocation no longer counts as revoked — the token has expired anyway. This decouples correctness from cleanup: a scheduled job can prune the table at leisure (the `idx_security_revoked_token_expires_at` index supports this) without any race against active checks.
## Schema
The adapter targets a single table. A reference PostgreSQL DDL ships at
`src/main/resources/org/fireflyframework/security/adapter/r2dbc/schema-postgresql.sql`:
```sql
CREATE TABLE IF NOT EXISTS security_revoked_token (
token_id VARCHAR(512) PRIMARY KEY,
expires_at TIMESTAMPTZ
);
CREATE INDEX IF NOT EXISTS idx_security_revoked_token_expires_at
ON security_revoked_token (expires_at);
```
Apply it the way your application manages schema — Flyway, Liquibase, or `spring.sql.init.*`. The adapter does not create or migrate the table itself.
## Requirements
- Java 21+
- Spring Boot 3.x with a reactive (R2DBC) data stack
- A relational database with an R2DBC driver. The reference schema and the integration test target **PostgreSQL** (`r2dbc-postgresql`); the SQL used (`ON CONFLICT`, `TIMESTAMPTZ`, `now()`) is PostgreSQL-flavored.
## Installation
The version is managed by the Firefly parent/BOM, so you can omit it:
```xml
org.fireflyframework
fireflyframework-security-adapter-r2dbc
```
If you are not inheriting the Firefly parent, pin the version explicitly:
```xml
org.fireflyframework
fireflyframework-security-adapter-r2dbc
26.06.01
```
Choose **this adapter or** `fireflyframework-security-adapter-redis` for `RevocationPort` — not both.
## Usage
Provide the adapter as the application's `RevocationPort` bean, wired from a Spring R2DBC `DatabaseClient`:
```java
import org.fireflyframework.security.adapter.r2dbc.R2dbcRevocationAdapter;
import org.fireflyframework.security.spi.RevocationPort;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.r2dbc.core.DatabaseClient;
@Configuration
class SecurityRevocationConfig {
@Bean
RevocationPort revocationPort(DatabaseClient databaseClient) {
return new R2dbcRevocationAdapter(databaseClient);
}
}
```
Application code (or the resource server) then uses the port:
```java
// Revoke a token until its own expiry (e.g. on logout)
revocationPort.revoke(jti, accessTokenExpiry).then(...);
// Fail-closed check during bearer validation
revocationPort.isRevoked(jti)
.flatMap(revoked -> revoked ? Mono.error(new TokenRevokedException()) : continueChain());
```
With `spring.r2dbc.*` configured and the `security_revoked_token` table present, no further setup is needed.
## Dependencies
Compile scope (kept deliberately minimal — one capability, no vendor lock-in beyond R2DBC):
- `fireflyframework-security-api` — neutral security domain.
- `fireflyframework-security-spi` — defines `RevocationPort`.
- `spring-r2dbc` — provides `DatabaseClient`.
- `slf4j-api`, `lombok` (provided).
Test scope only: `spring-boot-starter-data-r2dbc`, `spring-boot-starter-test`, `r2dbc-postgresql`, `testcontainers` (`postgresql` + `junit-jupiter`), and `reactor-test`.
## Testing
`R2dbcRevocationAdapterIntegrationTest` is a **real end-to-end integration test** — no mocks, no in-memory stand-in. It boots a `@SpringBootTest` against a live **PostgreSQL 16** instance started by **Testcontainers** (`postgres:16-alpine`, Docker required), wires `spring.r2dbc.*` from the container via `@DynamicPropertySource`, and loads the schema through `spring.sql.init.mode=always`.
The test exercises the full contract of the adapter:
- a revoked, unexpired token reports `isRevoked == true`;
- an unknown token reports `false` (the not-revoked default);
- an entry whose `expires_at` is in the past no longer counts as revoked;
- `revoke` is an idempotent upsert (revoking the same `token_id` twice updates, not duplicates).
It is named `*Test` so it runs under Surefire — the module does not bind the Failsafe plugin. Running the suite requires a working Docker environment.
```bash
mvn -pl fireflyframework-security-adapter-r2dbc test
```
## Documentation
- Firefly Framework documentation hub and module catalog: [github.com/fireflyframework](https://github.com/fireflyframework)
- Driven-port contract: `fireflyframework-security-spi` (`RevocationPort`)
- Cache-backed alternative: `fireflyframework-security-adapter-redis`
## License
Copyright 2024-2026 Firefly Software Foundation.
Licensed under the Apache License, Version 2.0. See [LICENSE](LICENSE) for details.