Skip to content
Flag of Europe
Made in the European Union · Independently built · Released under EUPL 1.2
Architecture

Architecture

Module Structure

The reactor has 22 modules as of 00.72.00 — the 13 from 00.70, the two opt-in credential modules from 00.71 (security-crypto-bc, security-credentials-hibp), and seven developer-experience modules from 00.72 (the security-dx-* facades, security-autoservice-*, and security-vaadin-starter — see Developer Experience). The table below lists the ten core/library modules; the opt-in credential and DX modules are covered on their own pages.

ModuleArtifactDescription
security-coresecurity-coreFramework-neutral concepts, every SPI contract, 11 persistence-store interfaces, the SecurityVersion stack, account-lifecycle / token / rate-limit services
security-vaadinsecurity-vaadinVaadin adapter — navigation security, SecurityVersion enforcer listener, secured components, SessionManagementView
security-restsecurity-restFramework-light REST adapter — RestSecurityVersionFilter, OpenApiSecurityMetadataGenerator
security-standalonesecurity-standaloneCore-Java adapter — dynamic-proxy SecuredProxy.wrap(…) for CLI / desktop / batch / embedded apps
security-testsecurity-testReusable test fixtures + JUnit-5 SecurityTestExtension
security-processorsecurity-processorCompile-time annotation processor for @Secured concrete classes
security-persistence-testkitsecurity-persistence-testkitContract test suites for every persistence-store SPI
security-persistence-eclipsestoresecurity-persistence-eclipsestoreEclipse-Store reference impl of every persistence-store SPI
demo-rest-shareddemo-rest-sharedTransport-level constants + tiny JSON helper
demo-vaadindemo-vaadinStandalone Vaadin demo (WAR) — auth runs in-JVM
demo-restdemo-restRunnable REST reference: JDK-only HTTP server + CLI client
demo-vaadin-rest-clientdemo-vaadin-rest-clientVaadin demo where demo-rest is the authoritative backend
demo-standalonedemo-standaloneInteractive Core-Java CLI demonstrating both method-security paths

Dependency Rules

security-core                       -> (no project deps; no third-party runtime deps)
security-vaadin                     -> security-core
security-rest                       -> security-core
security-standalone                 -> security-core
security-test                       -> security-core (test fixtures)
security-processor                  -> proxybuilder (compile-time only)
security-persistence-testkit        -> security-core (test scope)
security-persistence-eclipsestore   -> security-core, org.eclipse.store:storage-embedded
demo-*                              -> their adapter + security-core

security-core has no Vaadin, Servlet, REST-framework, or storage dependencies. None of the adapters depend on each other. Only security-persistence-eclipsestore carries a third-party storage dependency — the persistence SPIs themselves live dependency-free in the core. See Persistence.

Package layout

security-core packages are organised by concern, not by adapter:

com.svenruppert.vaadin.security
├── action/         — ActionAuthorizationService, ActionPermission
├── audit/          — SecurityAuditService + 27 sealed AuditEvent types + sinks
├── authentication/ — AuthenticationService, PasswordHasher, Pbkdf2PasswordHasher
├── authorization/  — annotations, evaluators, scanner, AuthorizationDecision
├── bootstrap/      — first-run admin setup
├── bruteforce/     — LoginAttemptPolicy + InMemory default
├── logout/         — LogoutService, SubjectSessionRegistry, LogoutListener
└── session/        — SessionPolicy + TimeoutSessionPolicy + SessionDecision

Each adapter adds one small wiring package on top:

com.svenruppert.vaadin.security.standalone   — Secured, StandaloneLoginFlow,
                                                ThreadLocalSubjectStore   (3 classes)
com.svenruppert.vaadin.security.logout.vaadin — VaadinLogoutService + Gateway
com.svenruppert.vaadin.security.session.vaadin— SessionLifetimeListener
com.svenruppert.vaadin.security.rest          — RestRequest/Response, filters,
                                                BearerTokenExtractor, …

Core rule

Library modules do not define project-specific permissions. Concrete roles, permissions, and business operations belong to consuming applications or demo modules.

Decision Model

The library uses two decision types:

TypeModulePurpose
AuthorizationDecisionsecurity-coreAdapter-neutral: Granted / Unauthenticated / Forbidden
AccessDecisionsecurity-coreVaadin-oriented (legacy, kept for backward compatibility)

Adapters map these to framework-specific behavior:

  • security-vaadin → navigation: continue, reroute to login, or reroute to error.
  • security-rest → HTTP status: 200/handler, 401, or 403.

In addition, several sealed decision hierarchies cover the production-hardening SPIs:

  • LoginAttemptDecision = Allowed | LockedOut(Duration, int) — see Brute-Force Protection
  • SessionDecision = Continue | RequireLogin | Invalidate(String reason, String loginRoute) — see Session Policy
  • SessionPolicyDecision = Active | IdleTimeout | AbsoluteLifetimeExceeded — pure-query result of SessionPolicy.evaluate(SessionMetadata)
  • PolicyDecision = Allowed | Denied | StepUpRequired — see Policy API
  • SecurityVersionStatus = Current(at) | Drifted(snapshot, current) and EnforcementOutcome = Continue | SessionStale(status) — see Multi-Tenancy
  • RateLimitDecision = Allowed | Throttled(eventsInWindow, limit, window, retryAfter)

SecurityServiceResolver — one resolver for all SPIs

SecurityAuditService audit    = SecurityServiceResolver.auditService();
LogoutService        logout   = SecurityServiceResolver.logoutService();
LoginAttemptPolicy   bruteFor = SecurityServiceResolver.loginAttemptPolicy();
SessionPolicy<MyUser> session = SecurityServiceResolver.sessionPolicy();
ActionAuthorizationService<MyUser> action = SecurityServiceResolver.actionService();
PasswordHasher       hasher   = SecurityServiceResolver.passwordHasher();

Covers all eight SPIs (Authentication / Authorization / Audit / Action / LoginAttempt / Session / PasswordHasher / Logout). Strict accessors throw IllegalStateException for missing services; find…() returns Optional; set…(…) is a programmatic test seam.

Annotation-Driven Protection

SecurityAnnotationScanner scans classes, methods, or any AnnotatedElement for restriction annotations meta-annotated with @SecurityAnnotation. Both adapters use the same scanner.

Generic annotations (in security-core):

  • @RequiresRole({"ROLE_ADMIN"})RequiresRoleEvaluator
  • @RequiresPermission("document:delete")RequiresPermissionEvaluator
  • @ProtectedBy(...)ProtectedByEvaluator

Project-specific annotations are encouraged for Vaadin views (e.g. @VisibleFor).

Three Authorization Patterns

The library distinguishes three intent-explicit call shapes:

// Pattern A — UX hint. Hide the button if the user can't use it.
if (PermissionGuard.hasPermission(user, "document:delete")) {
  layout.add(deleteButton);
}

// Pattern B — Server-side guard. Throws AccessDeniedException.
public void handleDelete() {
  PermissionGuard.requirePermission(user, "document:delete");
  documentService.delete(...);
}

// Pattern C — Audited action check. ActionAuthorizationService SPI.
public void handleDelete() {
  actionService.requireAllowed(user, ActionPermission.of("document:delete"));
  // ActionDenied audit event is emitted automatically on denial
  documentService.delete(...);
}

Hiding a button is never the security boundary — it’s a usability hint. The real check happens at the route, the handler, or the service call. The three patterns make the intent explicit at the call site so reviewers can tell the difference at a glance.

The two-tier setup in demo-vaadin-rest-client takes this further: PermissionGuard runs locally against the cached RemoteUser purely for UX, while the REST backend is the authoritative decision point. Clients never make local authorization decisions that the server hasn’t sanctioned.

Two-tier reference architecture

demo-vaadin-rest-client shows how to wire demo-rest as the authoritative backend behind a Vaadin UI:

  • Vaadin code sees no REST calls. All views/ and security/ code is free of java.net.http.*, URI, HttpClient, JSON, or endpoint paths. The contract is DemoBackendClient; HttpDemoBackendClient is the only class with transport knowledge.
  • REST server is authoritative. Mutating clicks call the server. 200 / 401 / 403 decides. Local PermissionGuard checks against the cached subject are UX hints only.
  • Bootstrap goes over REST. The Vaadin /setup view calls POST /api/bootstrap/admin — no in-JVM admin logic.

demo-rest-shared provides the transport-level constants (DemoEndpoints) and a tiny JSON helper, shared between the REST server and any client. It has no project-specific code.

Reusable security building blocks

TypeModule / packagePurpose
SecurityServiceResolversecurity-core/.../authorization/apiCentral SPI cache for all eight services.
PermissionGuard, AccessDeniedExceptionsecurity-core/.../authorization/apiStateless hasPermission / requirePermission (and role variants).
AuthenticationService<T,U>security-core/.../authenticationSPI: credential validation + subject loading.
PasswordHasher, PasswordHash, Pbkdf2PasswordHashersecurity-core/.../authenticationHash + verify + needsRehash drift detection. Demos rehash transparently on login.
LogoutService, LogoutScope, SubjectId, SubjectSessionRegistry, InMemorySubjectSessionRegistry, LogoutListener, SubjectClearingLogoutServicesecurity-core/.../logoutMulti-session logout. See Logout Flows.
VaadinLogoutServicesecurity-vaadinRegisters as a LogoutListener; invalidates Vaadin + HTTP sessions, redirects browser.
LoginAttemptPolicy, LoginAttemptDecision, InMemoryLoginAttemptPolicy, LoginAttemptConfiguration[Loader]security-core/.../bruteforcePluggable login throttling. See Brute-Force Protection.
SessionPolicy<U>, SessionDecision, SessionMetadata, TimeoutSessionPolicysecurity-core/.../sessionIdle / absolute lifetime + session-id rotation. See Session Policy.
SecurityAuditService, sealed AuditEvent (27 record types), RingBufferAuditSink, LoggingAuditSink, CompositeAuditService, DefaultCompositeAuditService, StoreBackedSecurityAuditServicesecurity-core/.../auditTyped publish/query pipeline. Powers the Vaadin /audit route and the REST GET /api/audit endpoint. See Security Audit.
ActionAuthorizationService<U>, ActionPermission, StaticActionAuthorizationServicesecurity-core/.../actionStable SPI for isAllowed/requireAllowed with auto-audit on denial.
StaticRolePermissionMapping, RolePermissionResolver…/authorization/api/permissionsImmutable role → permissions map; permission merge across roles.
SecuredOperationDescriptor, SecuredOperationRegistry, OperationVisibilityService…/authorization/api/operationsGeneric operation discovery with subject-aware filtering.
BootstrapConfigurationLoader, BootstrapStatussecurity-core/.../bootstrapCentralised sysprop+env+default loading; leak-safe status snapshot.
RestHeaders, BearerTokenExtractorsecurity-restCase-insensitive header lookup and Bearer-token parsing.
RestAuthenticationFilter, RestAuthorizationFiltersecurity-rest401-only and full 401/403 filters. The authorization filter additionally consults SessionPolicy.evaluate(...) when subject metadata is available.
BodyRestRequestsecurity-restBody-capable RestRequest. Avoids concrete-class casts.
BootstrapRestStatusMappersecurity-restInitialAdminCreationResult → HTTP status + stable error code.
Secured.wrap(Class<T>, T), Secured.requireAllowed(Class<?>, String)security-standaloneDynamic-proxy enforcement of @RequiresRole / @RequiresPermission on any interface — no framework needed. See Standalone Integration.
StandaloneLoginFlow<T,U>, LoginResult<U>, ThreadLocalSubjectStoresecurity-standaloneLogin lifecycle for CLI / desktop / batch apps; integrates with the same brute-force + audit SPIs as the framework adapters.

Quality — Mutation Coverage

Numbers below are the latest PIT measurement of the current source (the 00.72 line). Each module links to its full site-native report — per-mutator breakdown and the surviving-mutant list. The parent POM’s pitest-test-classes typo (junit.com.svenruppert.*com.svenruppert.*) was fixed in 00.70, so these are accurate figures.

Module00.60.0000.70.00latestTestsReport
security-core79 %86 %87 % (1893 / 2196)956report
security-vaadin90 %79 % ¹79 % (242 / 305)172report
security-rest95 %95 %95 % (86 / 91)71report
security-standalone98 %97 %97 % (33 / 34)30report
security-processor82 %75 % ² (46 / 61)report
security-persistence-eclipsestore70 %70 % (224 / 328)104report

New opt-in modules at a first PIT pass (recorded as a baseline; uplift tracked for 00.73):

ModuleAddedlatestReport
security-crypto-bc00.7161 % (110 / 181)report
security-credentials-hibp00.7153 % (38 / 74)report
security-dx00.7249 % (47 / 96)
security-dx-vaadin00.7261 % (14 / 23)
security-dx-rest00.7254 % (15 / 28)
security-dx-standalone00.7243 % (9 / 21)
security-vaadin-starter00.7266 % (49 / 74)
security-autoservice-processor00.7252 % (34 / 65)

¹ security-vaadin dipped at 00.70 because the Phase-4c enforcer listener and the Phase-8 secured components (SecuredButton, SecuredRouterLink, SecuredMenuItem, SessionManagementView) landed. The gap is dominated by VoidMethodCallMutator survivors on component-construction setters with no testable side effect. Absolute kill count rose from ~91 (00.60) to 242 (00.70).

² security-processor keeps 100 % line coverage; the module grew on the 00.72 line (28 → 61 mutations) as the diagnostics/wrapper-index code landed, so the kill rate now reads 75 % at the first PIT pass over the larger surface. The report lists the survivors (mostly BooleanFalseReturnValsMutator guard-return flips).

security-test and security-persistence-testkit are test-support modules (fixtures / contracts) and are not PIT targets themselves. security-autoservice-annotations is annotation-only (nothing to mutate). Demo modules carry the 00.60 baseline and were not re-PIT’d.

Reports are generated with Pitest via ./mvnw -P mutation verify.

Stable vs. Experimental API

Stable: role-based access, REST adapter contracts, SecuritySubject, AccessContext, AuthorizationDecision, scanner, LogoutService, LoginAttemptPolicy, SessionPolicy, SecurityAuditService, ActionAuthorizationService, PasswordHasher, SecurityServiceResolver.

Experimental (marked with @ExperimentalSecurityApi): permission-based access types — PermissionBasedAccessEvaluator, PermissionName, HasPermissions, PermissionAuthorizationService. May change in incompatible ways in future releases.

Project-specific permissions live in applications

Library modules contain no concrete business permissions. Examples like document:read belong in demo-rest. Real applications define their own catalog (e.g. shortlink:create, audit:read) inside the consuming project.