{"id":19168435,"url":"https://github.com/vaadin-developer/security-for-flow","last_synced_at":"2026-05-14T08:44:02.627Z","repository":{"id":44158136,"uuid":"195641486","full_name":"vaadin-developer/security-for-flow","owner":"vaadin-developer","description":"Proof Of Concept for a Security AddOn","archived":false,"fork":false,"pushed_at":"2026-05-13T15:50:40.000Z","size":1157,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":5,"default_branch":"develop","last_synced_at":"2026-05-13T17:33:51.268Z","etag":null,"topics":["flow","java","jetty","kotlin","poc","ruppert","security","sven","vaadin"],"latest_commit_sha":null,"homepage":null,"language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"eupl-1.2","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/vaadin-developer.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":"docs/security-modules.md","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":"2019-07-07T11:09:19.000Z","updated_at":"2026-05-13T15:51:31.000Z","dependencies_parsed_at":"2022-07-30T10:07:58.739Z","dependency_job_id":"d006b8c3-3376-4bda-a3ba-17c7a29c62a0","html_url":"https://github.com/vaadin-developer/security-for-flow","commit_stats":null,"previous_names":["vaadin-developer/flow-security"],"tags_count":6,"template":false,"template_full_name":"svenruppert/nano-vaadin-jetty","purl":"pkg:github/vaadin-developer/security-for-flow","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vaadin-developer%2Fsecurity-for-flow","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vaadin-developer%2Fsecurity-for-flow/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vaadin-developer%2Fsecurity-for-flow/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vaadin-developer%2Fsecurity-for-flow/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/vaadin-developer","download_url":"https://codeload.github.com/vaadin-developer/security-for-flow/tar.gz/refs/heads/develop","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vaadin-developer%2Fsecurity-for-flow/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33017709,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-13T13:14:54.681Z","status":"online","status_checked_at":"2026-05-14T02:00:06.663Z","response_time":57,"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":["flow","java","jetty","kotlin","poc","ruppert","security","sven","vaadin"],"created_at":"2024-11-09T09:42:39.027Z","updated_at":"2026-05-14T08:44:02.617Z","avatar_url":"https://github.com/vaadin-developer.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Security for Flow\n\nPluggable authentication, authorization, and annotation-driven protection for\nVaadin Flow, lightweight REST, and plain-Java / desktop / CLI applications.\nUses Java SPI (`ServiceLoader`) for application-provided services.\n\nThe library is split into a framework-neutral core, three adapters\n(Vaadin, REST, standalone), one transport-level shared module, and four\nreference demos. Concrete roles and permissions live in applications or\ndemo modules — never in the library.\n\n## Module Structure\n\n| Module | Artifact | Description |\n|---|---|---|\n| `security-core` | `security-core` | Generic, framework-neutral security concepts and decision logic |\n| `security-vaadin` | `security-vaadin` | Vaadin Flow adapter — view and navigation security |\n| `security-rest` | `security-rest` | Framework-light REST adapter — request and handler security |\n| `security-standalone` | `security-standalone` | Plain-Java / desktop / CLI adapter — ThreadLocal subject + dynamic-proxy method-level enforcement |\n| `demo-rest-shared` | `demo-rest-shared` | Transport-level constants + tiny JSON helper, shared between the REST server and any client |\n| `demo-vaadin` | `demo-vaadin` | Standalone Vaadin demo (WAR) — auth runs in-JVM |\n| `demo-rest` | `demo-rest` | Runnable REST reference: JDK-only HTTP server + CLI client |\n| `demo-vaadin-rest-client` | `demo-vaadin-rest-client` | Vaadin demo where `demo-rest` is the authoritative backend; UI talks to it through one encapsulated Java client |\n| `demo-standalone` | `demo-standalone` | Interactive CLI demo (library-borrowing) showing `Secured.wrap(...)` + `StandaloneLoginFlow` |\n\n### Dependency Rules\n\n```text\nsecurity-core              -\u003e (no project deps)\nsecurity-vaadin            -\u003e security-core\nsecurity-rest              -\u003e security-core\nsecurity-standalone        -\u003e security-core\ndemo-rest-shared           -\u003e (no project deps; transport-only)\ndemo-vaadin                -\u003e security-core, security-vaadin\ndemo-rest                  -\u003e security-core, security-rest, demo-rest-shared\ndemo-vaadin-rest-client    -\u003e security-core, security-vaadin, demo-rest-shared\n                              (test scope only: demo-rest)\ndemo-standalone            -\u003e security-core, security-standalone\n```\n\n`security-core` has no Vaadin, Servlet, or REST-framework dependencies.\nThe three adapter modules never depend on each other.\n\n## Quick Start\n\n### Build\n\n```bash\n# Full build (requires Maven 3.9.9+, Java 26+)\nmvn clean install\n```\n\n`mvn install` is required at least once because the demos depend on\neach other through the local `~/.m2` repository (see § *Module\nStructure* — `demo-vaadin-rest-client` depends on `demo-rest` for tests,\nand `demo-rest-shared` is consumed by both REST-side modules).\n\n### Pick the right demo\n\n| You want to see … | Run |\n|---|---|\n| Vaadin role/permission UI in a single JVM, no backend | [`demo-vaadin`](docs/demo-vaadin.md) |\n| Pure REST security (HTTP server + interactive CLI), no UI | [`demo-rest`](docs/demo-rest.md) |\n| Vaadin UI talking to a separate REST backend (real two-tier setup) | [`demo-vaadin-rest-client`](docs/demo-vaadin-rest-client.md) |\n| Plain-Java / CLI / desktop integration (no HTTP, no Vaadin) | `mvn -pl demo-standalone exec:java -Dexec.mainClass=com.svenruppert.vaadin.security.demo.standalone.DemoApp` |\n\n### `demo-vaadin` — Standalone Vaadin demo\n\n```bash\ncd demo-vaadin \u0026\u0026 mvn jetty:run\n# Browser: http://localhost:8080/\n```\n\nFirst run shows the bootstrap setup (the demo prints a token to the\nconsole). After setup, log in as the chosen admin. Demo users\n`user/user` and `demo/demo` are pre-populated; `admin` is created via\nthe bootstrap flow. Walkthrough: [`docs/demo-vaadin.md`](docs/demo-vaadin.md).\n\n### `demo-rest` — REST server + CLI\n\n```bash\n# Terminal 1 — JDK-only HTTP server on http://localhost:8080\nmvn -pl :demo-rest exec:java\n# Prints a bootstrap token to the console (TRANSIENT_CONSOLE mode).\n\n# Terminal 2 — interactive CLI\nmvn -pl :demo-rest exec:java \\\n    -Dexec.mainClass=com.svenruppert.vaadin.security.demo.rest.cli.DemoRestCli\n# Use `init-admin` to create the first admin via the bootstrap token.\n# Then `login admin \u003cnew-password\u003e` and play with `operations` / `call …`.\n```\n\nDemo users: `editor/editor`, `viewer/viewer`. `admin` is created via\nthe bootstrap flow; with `-Dsecurity.bootstrap.mode=DISABLED` the\ndefault `admin/admin` is pre-populated instead. Walkthrough:\n[`docs/demo-rest.md`](docs/demo-rest.md).\n\n### `demo-vaadin-rest-client` — Vaadin UI + REST backend\n\n```bash\n# Terminal 1 — backend (same as the REST demo above)\nmvn -pl :demo-rest exec:java\n# Prints a bootstrap token to the console.\n\n# Terminal 2 — Vaadin UI\nmvn -pl :demo-vaadin-rest-client jetty:run\n# Browser: http://localhost:9090/\n```\n\nBrowser opens `/setup` (because the backend has no admin yet). Paste\nthe token from the backend console, choose a username and password,\nsubmit — the **Vaadin UI calls** `POST /api/bootstrap/admin` against\nthe backend, no in-JVM auth. Then log in. The UI never speaks HTTP\ndirectly: only the encapsulated `DemoBackendClient` does.\nWalkthrough: [`docs/demo-vaadin-rest-client.md`](docs/demo-vaadin-rest-client.md).\n\n### `demo-standalone` — Interactive CLI\n\n```bash\nmvn -pl demo-standalone exec:java \\\n    -Dexec.mainClass=com.svenruppert.vaadin.security.demo.standalone.DemoApp\n```\n\nDemo users are seeded: `admin/admin`, `librarian/librarian`,\n`alice/alice`. After login, commands cover `list`, `borrow \u003ctitle\u003e`,\n`return \u003ctitle\u003e`, `add \u003ctitle\u003e` (LIBRARIAN+), `remove \u003ctitle\u003e` (ADMIN\nonly). Calls run through a `Secured.wrap(LibraryService.class, ...)`\nproxy that enforces the method-level `@RequiresPermission` /\n`@RequiresRole` annotations; rejections surface as `DENIED — …` lines\nin the terminal.\n\n### Tests\n\n```bash\n# Whole reactor — ~570 tests across all modules\nmvn test\n\n# Single module\nmvn -pl :security-core -am test\nmvn -pl :demo-rest -am test\nmvn -pl :demo-vaadin-rest-client -am test\n```\n\n### Add the dependency\n\nFor a Vaadin Flow application:\n\n```xml\n\u003cdependency\u003e\n  \u003cgroupId\u003ecom.svenruppert\u003c/groupId\u003e\n  \u003cartifactId\u003esecurity-vaadin\u003c/artifactId\u003e\n  \u003cversion\u003e00.51.01-SNAPSHOT\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\nFor a REST handler / servlet application:\n\n```xml\n\u003cdependency\u003e\n  \u003cgroupId\u003ecom.svenruppert\u003c/groupId\u003e\n  \u003cartifactId\u003esecurity-rest\u003c/artifactId\u003e\n  \u003cversion\u003e00.51.01-SNAPSHOT\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\nFor a plain-Java / desktop / CLI application:\n\n```xml\n\u003cdependency\u003e\n  \u003cgroupId\u003ecom.svenruppert\u003c/groupId\u003e\n  \u003cartifactId\u003esecurity-standalone\u003c/artifactId\u003e\n  \u003cversion\u003e00.51.01-SNAPSHOT\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\n`security-core` is pulled in transitively by any of the three adapters.\n\n## Vaadin Integration\n\nTo secure a Vaadin Flow application, implement the following SPI contracts and\nregister them via `META-INF/services/` files. Reference: `demo-vaadin`.\n\n### 1. Define a user type\n\n```java\npublic record MyUser(String username, Set\u003cString\u003e roles) {}\n```\n\n### 2. Implement `AuthenticationService\u003cT, U\u003e`\n\nValidates credentials and loads the user subject.\n\n```java\npublic class MyAuthenticationService\n    implements AuthenticationService\u003cCredentials, MyUser\u003e {\n\n  @Override\n  public boolean checkCredentials(Credentials credentials) { /* ... */ }\n\n  @Override\n  public MyUser loadSubject(Credentials credentials) { /* ... */ }\n\n  @Override\n  public Class\u003cMyUser\u003e subjectType() { return MyUser.class; }\n}\n```\n\nRegister in `META-INF/services/com.svenruppert.vaadin.security.authentication.AuthenticationService`:\n```\ncom.example.MyAuthenticationService\n```\n\n### 3. Implement `AuthorizationService\u003cU\u003e`\n\nMaps a user to roles. Only `rolesFor()` is required — `permissionsFor()` has a\ndefault implementation returning empty permissions.\n\n```java\npublic class MyAuthorizationService implements AuthorizationService\u003cMyUser\u003e {\n  @Override\n  public HasRoles rolesFor(MyUser subject) { /* ... */ }\n}\n```\n\nRegister in `META-INF/services/com.svenruppert.vaadin.security.authorization.api.AuthorizationService`.\n\n### 4. Define a restriction annotation with `@SecurityAnnotation`\n\n```java\n@Retention(RUNTIME)\n@SecurityAnnotation(MyRoleAccessEvaluator.class)\npublic @interface VisibleFor {\n  MyRole[] value();\n}\n```\n\nOr use the generic annotations from `security-core`:\n\n```java\n@RequiresRole(\"ROLE_ADMIN\")\n@RequiresPermission(\"demo:edit\")\n```\n\n### 5. Implement `AccessEvaluator`\n\n```java\npublic class MyRoleAccessEvaluator\n    implements AccessEvaluator\u003cVisibleFor\u003e {\n\n  @Override\n  public AccessDecision evaluate(AccessContext context, VisibleFor annotation) {\n    // return AccessDecision.granted() or AccessDecision.denied(\"login\", false)\n  }\n}\n```\n\nOr extend `RoleBasedAccessEvaluator`:\n\n```java\npublic class MyRoleAccessEvaluator\n    extends RoleBasedAccessEvaluator\u003cVisibleFor, MyUser\u003e {\n\n  @Override\n  public Set\u003cRoleName\u003e requiredRoles(VisibleFor annotation) { /* ... */ }\n\n  @Override\n  public String alternativeNavigationTarget(\n      AccessContext context, VisibleFor annotation) { /* ... */ }\n}\n```\n\nRegister in `META-INF/services/com.svenruppert.vaadin.security.authorization.api.AccessEvaluator`.\n\n### 6. Extend `LoginListener\u003cU\u003e`\n\n```java\npublic class MyLoginListener extends LoginListener\u003cMyUser\u003e {\n  @Override\n  public Class\u003c? extends LoginView\u003e loginNavigationTarget() {\n    return MyLoginView.class;\n  }\n  @Override\n  public Class\u003c? extends Component\u003e defaultNavigationTarget() {\n    return MainView.class;\n  }\n}\n```\n\nRegister in `META-INF/services/com.svenruppert.vaadin.security.authorization.LoginListener`.\n\n### 7. Extend `LoginView`\n\nCreate your login UI by extending the abstract `LoginView` base class.\n\n### 8. Annotate route views\n\n```java\n@Route(\"admin\")\n@VisibleFor(MyRole.ADMIN)\npublic class AdminView extends Div { /* ... */ }\n```\n\n## REST Integration\n\nTo secure REST handlers, implement `RestSubjectResolver`, annotate handlers with\ngeneric permission annotations, and run them through `RestAuthorizationFilter`.\n\nA complete runnable reference lives in `demo-rest`: a JDK-only HTTP server\n(`com.sun.net.httpserver.HttpServer`) and an interactive CLI\n(`java.net.http.HttpClient`) demonstrating login, server-side operation\nfiltering, and the `200 / 401 / 403` decision flow. See\n[`docs/demo-rest.md`](docs/demo-rest.md) for run instructions and example\nsessions.\n\n### 1. Define project permissions and role mapping\n\n```java\npublic enum DemoPermission {\n  DOCUMENT_READ(\"document:read\"),\n  DOCUMENT_DELETE(\"document:delete\");\n\n  private final PermissionName permissionName;\n  // ...\n}\n```\n\n```java\npublic final class DemoRolePermissionMapping implements RolePermissionMapping {\n  @Override\n  public Set\u003cPermissionName\u003e permissionsFor(RoleName role) { /* ... */ }\n}\n```\n\n### 2. Implement `RestSubjectResolver`\n\n```java\npublic final class MyRestSubjectResolver implements RestSubjectResolver {\n\n  private static final BearerTokenExtractor BEARER = new BearerTokenExtractor();\n\n  @Override\n  public Optional\u003cSecuritySubject\u003e resolveSubject(RestRequest request) {\n    return BEARER.extract(request)        // case-insensitive Bearer parser\n        .flatMap(myTokenStore::resolve)\n        .map(this::toSubject);\n  }\n}\n```\n\nThe library does not enforce a token strategy. `BearerTokenExtractor` and\n`RestHeaders` (case-insensitive header lookup) live in `security-rest` —\nno need to roll your own.\n\n### 3. Annotate handlers\n\n```java\npublic final class DocumentHandlers {\n  @RequiresPermission(\"document:read\")\n  public void read(RestRequest request, RestResponse response) { /* ... */ }\n\n  @RequiresPermission(\"document:delete\")\n  public void delete(RestRequest request, RestResponse response) { /* ... */ }\n\n  @RequiresPermission(\"document:create\")\n  public void create(RestRequest request, RestResponse response) {\n    // Pattern-match instead of casting to a concrete adapter request type\n    if (request instanceof BodyRestRequest body) {\n      String json = body.bodyAsUtf8();\n      // ...\n    }\n  }\n}\n```\n\nUse `BodyRestRequest` (in `security-rest`) when a handler needs the request\nbody. Adapters supply the raw bytes; helpers decode UTF-8.\n\n### 4. Wire the filter\n\n```java\nRestAuthorizationFilter filter =\n    new RestAuthorizationFilter(new MyRestSubjectResolver());\n\nfilter.authorizeAndHandle(\n    request, response, handlers::delete, handlerMethod);\n```\n\nThe filter:\n\n1. Resolves the subject from the request.\n2. Scans the handler method/class for a security annotation.\n3. Builds an `AccessContext` with `resourceType=\"rest-endpoint\"`.\n4. Runs the matching `AuthorizationEvaluator`.\n5. Maps the decision: `Granted` runs the handler; `Unauthenticated` → `401`;\n   `Forbidden` → `403`. Error bodies are short and generic — no internals leak.\n\n### 5. Authenticated-only endpoints\n\nFor endpoints that need any authenticated subject but no specific permission\n(`/me`, `/logout`, …), use `RestAuthenticationFilter` instead of writing\nyour own subject check:\n\n```java\nRestAuthenticationFilter authFilter = new RestAuthenticationFilter(resolver);\nauthFilter.requireAuthenticated(request, response, handlers::me);\n// 401 with body \"Unauthorized\" if no subject; delegates otherwise\n```\n\n### 6. (Optional) Operation discovery filtered server-side\n\n`demo-rest` shows a `GET /api/operations` endpoint that returns only the\noperations the current subject is allowed to invoke. Built on\n`SecuredOperationRegistry` + `OperationVisibilityService` from\n`security-core` — the same permission model that protects the handlers is\nused to filter the discovery list. Clients never make local authorization\ndecisions.\n\n## Standalone Integration\n\nTo secure plain-Java code — desktop, CLI, daemon — annotate a service\ninterface, wrap implementations once with `Secured.wrap(...)`, and drive\nthe login lifecycle with `StandaloneLoginFlow`. There is no listener,\nno filter chain, no navigation phase; every method call on the wrapped\ninterface runs through the same `SecurityAnnotationScanner` + evaluator\nmachinery as the Vaadin and REST adapters.\n\nA complete runnable reference lives in `demo-standalone`: an\ninteractive library-borrowing CLI with three seeded users and a\nrole/permission matrix exercising both `@RequiresPermission` and\n`@RequiresRole`.\n\n### 1. Define the service interface\n\n```java\npublic interface LibraryService {\n  @RequiresPermission(\"book:list\")\n  List\u003cString\u003e listBooks();\n\n  @RequiresPermission(\"book:borrow\")\n  void borrowBook(String title);\n\n  @RequiresRole(\"ADMIN\")\n  void removeBook(String title);\n}\n```\n\n### 2. Wrap the implementation\n\n```java\nLibraryService secured =\n    Secured.wrap(LibraryService.class, new InMemoryLibraryService());\n\nsecured.listBooks();             // runs if the bound subject has book:list\nsecured.removeBook(\"x\");         // throws AccessDeniedException for non-ADMIN\n```\n\n`Secured.wrap(...)` returns a JDK dynamic-proxy implementing the\ninterface. Every call scans the method (then the declaring class) for a\n`@SecurityAnnotation`-meta-annotated annotation, runs the matching\nevaluator, and either delegates to the real implementation or throws\n`AccessDeniedException`. `Object` methods bypass enforcement.\n\nFor callbacks / lambdas where wrapping an interface is awkward, call\nthe single-shot helper:\n\n```java\nSecured.requireAllowed(MyOps.class, \"delete\");\n// throws AccessDeniedException if the calling subject is not allowed\n```\n\n### 3. Drive the login flow\n\n```java\nStandaloneLoginFlow\u003cCredentials, User\u003e flow = new StandaloneLoginFlow\u003c\u003e();\nLoginResult\u003cUser\u003e result = flow.login(new Credentials(\"alice\", \"alice\"), \"alice\");\n\nswitch (result) {\n  case LoginResult.Success\u003cUser\u003e s   -\u003e /* proceed */;\n  case LoginResult.Rejected\u003cUser\u003e r  -\u003e /* wrong credentials */;\n  case LoginResult.LockedOut\u003cUser\u003e l -\u003e /* throttled — retry in l.decision().remaining() */;\n}\n```\n\nThe flow consults `LoginAttemptPolicy.beforeAttempt(...)` first, then\ncalls the SPI-registered `AuthenticationService.checkCredentials` /\n`loadSubject`, binds the subject through the active `SubjectStore`,\nrecords success/failure on the policy, and publishes `LoginSucceeded` /\n`LoginFailed` to the `SecurityAuditService`. `flow.logout()` clears the\nSubjectStore for the current thread.\n\n### 4. SubjectStore — ThreadLocal by default\n\n`security-standalone` registers `ThreadLocalSubjectStore` as the SPI\n`SubjectStore`. It is **not** inherited across threads — a value bound\non the main thread is invisible to a background `Executor`. Propagating\nthe subject to worker threads is the application's responsibility:\ncapture the user before submitting work, then call\n`SubjectStores.subjectStore().setCurrentSubject(user, User.class)` on\nthe worker thread (or use a `Runnable` wrapper that does that).\n\n## Decision Model\n\nThe library uses two decision types:\n\n| Type | Module | Purpose |\n|---|---|---|\n| `AuthorizationDecision` | `security-core` | Adapter-neutral: `Granted` / `Unauthenticated` / `Forbidden` |\n| `AccessDecision` | `security-core` | Vaadin-oriented (legacy, kept for backward compatibility) |\n\nAdapters map these to framework-specific behavior:\n\n- `security-vaadin` → navigation: continue, reroute to login, or reroute to error.\n- `security-rest` → HTTP status: `200`/handler, `401`, or `403`.\n\n## Annotation-Driven Protection\n\n`SecurityAnnotationScanner` scans classes, methods, or any `AnnotatedElement`\nfor restriction annotations meta-annotated with `@SecurityAnnotation`. Both\nadapters use the same scanner.\n\nGeneric annotations (in `security-core`):\n\n- `@RequiresRole({\"ROLE_ADMIN\"})` → `RequiresRoleEvaluator`\n- `@RequiresPermission(\"document:delete\")` → `RequiresPermissionEvaluator`\n- `@ProtectedBy(...)` → `ProtectedByEvaluator`\n\nProject-specific annotations are encouraged for Vaadin views (e.g. `@VisibleFor`).\n\n## Reusable security building blocks\n\n| Type | Module / package | Purpose |\n|---|---|---|\n| `SecurityServiceResolver` | `security-core/.../authorization/api` | Central SPI cache. Strict accessors throw `IllegalStateException` for missing services; `find…()` returns `Optional`; `set…(…)` is a programmatic test seam. Covers Authentication / Authorization / Audit / Action / LoginAttempt / Session / PasswordHasher / Logout. |\n| `PermissionGuard` | `security-core/.../authorization/api` | Stateless `hasPermission` / `requirePermission` (and role variants) on any `HasPermissions`/`HasRoles`. Throws `AccessDeniedException`. |\n| `AuthenticationService\u003cT,U\u003e` | `security-core/.../authentication` | SPI: credential validation + subject loading. Adapter-neutral. |\n| `LogoutService` | `security-core/.../logout` | `logout(SubjectId, LogoutScope)` SPI, paired with `SubjectClearingLogoutService` default + `SubjectSessionRegistry` for multi-session logout. Vaadin-side: `VaadinLogoutService` rotates HTTP session and redirects. |\n| `LoginAttemptPolicy` + `InMemoryLoginAttemptPolicy` | `security-core/.../bruteforce` | Pluggable login throttling. Sealed `LoginAttemptDecision = Allowed \\| LockedOut(Duration, int)`. Configured via `LoginAttemptConfiguration[Loader]` (sysprop/env/default). |\n| `SessionPolicy\u003cU\u003e` + `TimeoutSessionPolicy` | `security-core/.../session` | Idle/absolute lifetime checks. `evaluate(SessionMetadata)` pure-query path consumed by `SessionLifetimeListener` (Vaadin) and the REST filters. `rotateSessionAfterLogin` honoured via `VaadinService.reinitializeSession(...)`. |\n| `SecurityAuditService`, sealed `AuditEvent` (16 record variants), `RingBufferAuditSink`, `LoggingAuditSink`, `CompositeAuditService`, `DefaultCompositeAuditService` | `security-core/.../audit` | Typed publish/query audit pipeline. `RingBufferAuditSink` backs the Vaadin `/audit`-route and the REST `GET /api/audit` endpoint. |\n| `ActionAuthorizationService\u003cU\u003e`, `ActionPermission`, `StaticActionAuthorizationService` | `security-core/.../action` | Stable SPI for `isAllowed`/`requireAllowed` action checks with `ACTION_DENIED` audit on denial. |\n| `PasswordHasher`, `PasswordHash`, `Pbkdf2PasswordHasher` | `security-core/.../authentication` | Hash + verify + `needsRehash` (drift detection); demos rehash transparently on successful login. |\n| `StaticRolePermissionMapping`, `RolePermissionResolver` | `…/api/permissions` | Immutable role → permissions map with a builder; permission-merge across roles. |\n| `SecuredOperationDescriptor`, `SecuredOperationRegistry`, `OperationVisibilityService` | `…/api/operations` | Generic operation discovery with subject-aware filtering. Adapter metadata (HTTP method, path, view class) goes into the descriptor's `attributes`. |\n| `BootstrapConfigurationLoader`, `BootstrapStatus` | `security-core/.../bootstrap` | Centralised sysprop+env+default loading with TTL parsing; leak-safe status snapshot. |\n| `RestHeaders`, `BearerTokenExtractor` | `security-rest` | Case-insensitive header lookup and Bearer-token parsing. |\n| `RestAuthenticationFilter`, `RestAuthorizationFilter` | `security-rest` | 401/403 filters; the authorization filter additionally consults `SessionPolicy.evaluate(...)` when subject-resolved metadata is available. |\n| `BodyRestRequest` | `security-rest` | Body-capable `RestRequest`. Avoids concrete-class casts in handlers. |\n| `BootstrapRestStatusMapper` | `security-rest` | `InitialAdminCreationResult` → HTTP status code + stable error code. |\n\n## Stable vs. Experimental API\n\n**Stable**: role-based access, REST adapter contracts, `SecuritySubject`,\n`AccessContext`, `AuthorizationDecision`, scanner.\n\n**Experimental** (marked with `@ExperimentalSecurityApi`): permission-based\naccess types — `PermissionBasedAccessEvaluator`, `PermissionName`,\n`HasPermissions`, `PermissionAuthorizationService`. May change in incompatible\nways in future releases.\n\n## Project-Specific Permissions Live in Applications\n\nLibrary modules contain no concrete business permissions. Examples like\n`document:read` belong in `demo-rest`. Real applications define their own\ncatalog (e.g. `shortlink:create`, `audit:read`) inside the consuming project.\n\nSee [`docs/security-modules.md`](docs/security-modules.md) for the full\nextension model.\n\n## First-run bootstrap\n\nBoth demos ship without any administrator account. The first administrator\nis created via a one-time **bootstrap token** in either `PERSISTENT_FILE`\nor `TRANSIENT_CONSOLE` mode. The same library powers the REST endpoint,\nthe CLI `init-admin` command, and the Vaadin `/setup` view. Token values\nare never written to logs, never echoed in responses, and the mechanism\nturns itself off once an administrator exists.\n\nConfigurable via system properties (preferred) or environment variables —\nboth read centrally by `BootstrapConfigurationLoader`:\n\n| System property | Environment variable | Default (demos) |\n|---|---|---|\n| `security.bootstrap.mode` | `SECURITY_BOOTSTRAP_MODE` | `TRANSIENT_CONSOLE` |\n| `security.bootstrap.token.file` | `SECURITY_BOOTSTRAP_TOKEN_FILE` | `./data/bootstrap.token` |\n| `security.bootstrap.token.ttl` | `SECURITY_BOOTSTRAP_TOKEN_TTL` | `PT24H` |\n\nSee [`docs/bootstrap.md`](docs/bootstrap.md) for modes, endpoints, and the\noperator workflow.\n\n## Roadmap\n\n`Konzept-V00.60.00.md` outlines further steps: `SecurityAuditService`,\n`LoginAttemptPolicy` (brute-force), minimal `SessionPolicy`, central\n`LogoutService`, and `ActionAuthorizationService` (`isAllowed` /\n`requireAllowed`). The bootstrap mechanism and `PasswordHasher`\nabstraction are now in place; the rest is pending.\n\n## License\n\nEUPL 1.2","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvaadin-developer%2Fsecurity-for-flow","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvaadin-developer%2Fsecurity-for-flow","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvaadin-developer%2Fsecurity-for-flow/lists"}