{"id":51342447,"url":"https://github.com/fireflyframework/fireflyframework-security-adapter-r2dbc","last_synced_at":"2026-07-02T09:01:17.259Z","repository":{"id":367530046,"uuid":"1273709983","full_name":"fireflyframework/fireflyframework-security-adapter-r2dbc","owner":"fireflyframework","description":"Firefly Framework — R2DBC security data adapter: token revocation list over PostgreSQL (real Postgres Testcontainer IT)","archived":false,"fork":false,"pushed_at":"2026-06-26T10:57:32.000Z","size":16,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-26T12:09:36.403Z","etag":null,"topics":["firefly-framework","hexagonal-architecture","java","microservices","postgresql","r2dbc","reactive","security","spring-boot","spring-security","token-revocation"],"latest_commit_sha":null,"homepage":null,"language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/fireflyframework.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-18T19:48:02.000Z","updated_at":"2026-06-26T10:17:54.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/fireflyframework/fireflyframework-security-adapter-r2dbc","commit_stats":null,"previous_names":["fireflyframework/fireflyframework-security-adapter-r2dbc"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/fireflyframework/fireflyframework-security-adapter-r2dbc","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fireflyframework%2Ffireflyframework-security-adapter-r2dbc","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fireflyframework%2Ffireflyframework-security-adapter-r2dbc/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fireflyframework%2Ffireflyframework-security-adapter-r2dbc/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fireflyframework%2Ffireflyframework-security-adapter-r2dbc/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/fireflyframework","download_url":"https://codeload.github.com/fireflyframework/fireflyframework-security-adapter-r2dbc/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fireflyframework%2Ffireflyframework-security-adapter-r2dbc/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35040024,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-02T02:00:06.368Z","response_time":173,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["firefly-framework","hexagonal-architecture","java","microservices","postgresql","r2dbc","reactive","security","spring-boot","spring-security","token-revocation"],"created_at":"2026-07-02T09:00:51.293Z","updated_at":"2026-07-02T09:01:17.069Z","avatar_url":"https://github.com/fireflyframework.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Firefly Framework - Security R2DBC Adapter\n\n[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](LICENSE)\n[![Java](https://img.shields.io/badge/Java-21%2B-orange.svg)](https://openjdk.org)\n[![Spring Boot](https://img.shields.io/badge/Spring%20Boot-3.x-green.svg)](https://spring.io/projects/spring-boot)\n\n\u003e 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.\n\n---\n\n## Table of Contents\n\n- [Overview](#overview)\n- [Where it sits](#where-it-sits-in-the-hexagonal-security-platform)\n- [What it provides](#what-it-provides)\n- [Schema](#schema)\n- [Requirements](#requirements)\n- [Installation](#installation)\n- [Usage](#usage)\n- [Dependencies](#dependencies)\n- [Testing](#testing)\n- [Documentation](#documentation)\n- [License](#license)\n\n## Overview\n\nThe 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.\n\nThis 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.\n\nThe 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.\n\n## Where it sits in the hexagonal security platform\n\n```\napi  -\u003e  spi  -\u003e  core  -\u003e  webflux  -\u003e  resource-server  -\u003e  adapters\n```\n\n| Layer | Module | Role |\n| --- | --- | --- |\n| `api` | `fireflyframework-security-api` | Driving ports + neutral domain (`SecurityPrincipal`, decisions, …). |\n| `spi` | `fireflyframework-security-spi` | Driven ports, including **`RevocationPort`**. |\n| `core` | `fireflyframework-security-core` | Vendor-neutral engine; depends on ports, never on adapters. |\n| `webflux` | `fireflyframework-security-webflux` | Reactive binding of the core to Spring WebFlux. |\n| `resource-server` | `fireflyframework-security-resource-server` | Bearer validation, revocation check, secure-by-default headers/CORS/CSRF. |\n| `adapters` | **`fireflyframework-security-adapter-r2dbc`** (this module), `-adapter-redis`, … | Concrete `RevocationPort` (and sibling-port) implementations. |\n\nThe 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.\n\n## What it provides\n\n### `R2dbcRevocationAdapter` (implements `org.fireflyframework.security.spi.RevocationPort`)\n\nA reactive, R2DBC-backed revocation list. Constructed with a single Spring `DatabaseClient`:\n\n```java\npublic R2dbcRevocationAdapter(DatabaseClient databaseClient)\n```\n\nIt implements the two `RevocationPort` operations:\n\n| Method | Behavior |\n| --- | --- |\n| `Mono\u003cVoid\u003e 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. |\n| `Mono\u003cBoolean\u003e isRevoked(String tokenId)` | Returns `true` only for an entry that exists **and is still active** (`expires_at IS NULL OR expires_at \u003e now()`). An unknown token returns `false` via `defaultIfEmpty(false)`. |\n\n**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.\n\n## Schema\n\nThe adapter targets a single table. A reference PostgreSQL DDL ships at\n`src/main/resources/org/fireflyframework/security/adapter/r2dbc/schema-postgresql.sql`:\n\n```sql\nCREATE TABLE IF NOT EXISTS security_revoked_token (\n    token_id   VARCHAR(512) PRIMARY KEY,\n    expires_at TIMESTAMPTZ\n);\n\nCREATE INDEX IF NOT EXISTS idx_security_revoked_token_expires_at\n    ON security_revoked_token (expires_at);\n```\n\nApply it the way your application manages schema — Flyway, Liquibase, or `spring.sql.init.*`. The adapter does not create or migrate the table itself.\n\n## Requirements\n\n- Java 21+\n- Spring Boot 3.x with a reactive (R2DBC) data stack\n- 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.\n\n## Installation\n\nThe version is managed by the Firefly parent/BOM, so you can omit it:\n\n```xml\n\u003cdependency\u003e\n    \u003cgroupId\u003eorg.fireflyframework\u003c/groupId\u003e\n    \u003cartifactId\u003efireflyframework-security-adapter-r2dbc\u003c/artifactId\u003e\n\u003c/dependency\u003e\n```\n\nIf you are not inheriting the Firefly parent, pin the version explicitly:\n\n```xml\n\u003cdependency\u003e\n    \u003cgroupId\u003eorg.fireflyframework\u003c/groupId\u003e\n    \u003cartifactId\u003efireflyframework-security-adapter-r2dbc\u003c/artifactId\u003e\n    \u003cversion\u003e26.06.01\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\nChoose **this adapter or** `fireflyframework-security-adapter-redis` for `RevocationPort` — not both.\n\n## Usage\n\nProvide the adapter as the application's `RevocationPort` bean, wired from a Spring R2DBC `DatabaseClient`:\n\n```java\nimport org.fireflyframework.security.adapter.r2dbc.R2dbcRevocationAdapter;\nimport org.fireflyframework.security.spi.RevocationPort;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.r2dbc.core.DatabaseClient;\n\n@Configuration\nclass SecurityRevocationConfig {\n\n    @Bean\n    RevocationPort revocationPort(DatabaseClient databaseClient) {\n        return new R2dbcRevocationAdapter(databaseClient);\n    }\n}\n```\n\nApplication code (or the resource server) then uses the port:\n\n```java\n// Revoke a token until its own expiry (e.g. on logout)\nrevocationPort.revoke(jti, accessTokenExpiry).then(...);\n\n// Fail-closed check during bearer validation\nrevocationPort.isRevoked(jti)\n        .flatMap(revoked -\u003e revoked ? Mono.error(new TokenRevokedException()) : continueChain());\n```\n\nWith `spring.r2dbc.*` configured and the `security_revoked_token` table present, no further setup is needed.\n\n## Dependencies\n\nCompile scope (kept deliberately minimal — one capability, no vendor lock-in beyond R2DBC):\n\n- `fireflyframework-security-api` — neutral security domain.\n- `fireflyframework-security-spi` — defines `RevocationPort`.\n- `spring-r2dbc` — provides `DatabaseClient`.\n- `slf4j-api`, `lombok` (provided).\n\nTest scope only: `spring-boot-starter-data-r2dbc`, `spring-boot-starter-test`, `r2dbc-postgresql`, `testcontainers` (`postgresql` + `junit-jupiter`), and `reactor-test`.\n\n## Testing\n\n`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`.\n\nThe test exercises the full contract of the adapter:\n\n- a revoked, unexpired token reports `isRevoked == true`;\n- an unknown token reports `false` (the not-revoked default);\n- an entry whose `expires_at` is in the past no longer counts as revoked;\n- `revoke` is an idempotent upsert (revoking the same `token_id` twice updates, not duplicates).\n\nIt is named `*Test` so it runs under Surefire — the module does not bind the Failsafe plugin. Running the suite requires a working Docker environment.\n\n```bash\nmvn -pl fireflyframework-security-adapter-r2dbc test\n```\n\n## Documentation\n\n- Firefly Framework documentation hub and module catalog: [github.com/fireflyframework](https://github.com/fireflyframework)\n- Driven-port contract: `fireflyframework-security-spi` (`RevocationPort`)\n- Cache-backed alternative: `fireflyframework-security-adapter-redis`\n\n## License\n\nCopyright 2024-2026 Firefly Software Foundation.\n\nLicensed under the Apache License, Version 2.0. See [LICENSE](LICENSE) for details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffireflyframework%2Ffireflyframework-security-adapter-r2dbc","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffireflyframework%2Ffireflyframework-security-adapter-r2dbc","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffireflyframework%2Ffireflyframework-security-adapter-r2dbc/lists"}