Architecture
Module Structure
The reactor has 44 modules as of 00.79.41 — the 13 from 00.70, the
two opt-in credential modules from 00.71 (jSentinel-crypto-bc,
jSentinel-credentials-hibp), seven developer-experience modules from
00.72 (the jSentinel-dx-* facades, jSentinel-autoservice-*, and
jSentinel-vaadin-starter — see Developer Experience),
three token-propagation modules from 00.74 (jSentinel-propagation,
jSentinel-propagation-processor, jSentinel-propagation-oidc — see
00.74.00 release notes), and four
Security-Event-Bus modules from 00.75 (jSentinel-events,
jSentinel-events-rest, jSentinel-events-testkit,
jSentinel-events-persistence-eclipsestore — see
00.75.00 release notes), and the opt-in
jSentinel-jwt JWT-validation module from 00.76 (Nimbus JOSE+JWT — see
00.76.00 release notes), three OAuth2
Relying-Party modules from 00.77 (jSentinel-oauth2, jSentinel-oauth2-vaadin,
jSentinel-oauth2-rest — see 00.77.00 release notes),
three OIDC Relying-Party modules from 00.78 (jSentinel-identity-oidc + the
Vaadin / REST adapters — see 00.78.00 release notes),
the six IdP vendor-profile modules plus a jSentinel-test-oidc harness from
00.79 (see 00.79.00 release notes), and the
jSentinel-dpop sender-constrained-token module from 00.79.10 (DPoP / RFC 9449 —
see 00.79.10 release notes).
The table below lists the ten core/library modules; the opt-in credential
and DX modules are covered on their own pages.
| Module | Artifact | Description |
|---|---|---|
jSentinel-core | jSentinel-core | Framework-neutral concepts, every SPI contract, 11 persistence-store interfaces, the JSentinelVersion stack, account-lifecycle / token / rate-limit services |
jSentinel-vaadin | jSentinel-vaadin | Vaadin adapter — navigation security, JSentinelVersion enforcer listener, secured components, SessionManagementView |
jSentinel-rest | jSentinel-rest | Framework-light REST adapter — RestSecurityVersionFilter, OpenApiSecurityMetadataGenerator |
jSentinel-standalone | jSentinel-standalone | Core-Java adapter — dynamic-proxy SecuredProxy.wrap(…) for CLI / desktop / batch / embedded apps |
jSentinel-test | jSentinel-test | Reusable test fixtures + JUnit-5 JSentinelTestExtension |
jSentinel-processor | jSentinel-processor | Compile-time annotation processor for @Secured concrete classes |
jSentinel-persistence-testkit | jSentinel-persistence-testkit | Contract test suites for every persistence-store SPI |
jSentinel-persistence-eclipsestore | jSentinel-persistence-eclipsestore | Eclipse-Store reference impl of every persistence-store SPI |
demo-rest-shared | demo-rest-shared | Transport-level constants + tiny JSON helper |
demo-vaadin | demo-vaadin | Standalone Vaadin demo (WAR) — auth runs in-JVM |
demo-rest | demo-rest | Runnable REST reference: JDK-only HTTP server + CLI client |
demo-vaadin-rest-client | demo-vaadin-rest-client | Vaadin demo where demo-rest is the authoritative backend |
demo-standalone | demo-standalone | Interactive Core-Java CLI demonstrating both method-security paths |
Dependency Rules
jSentinel-core -> (no project deps; no third-party runtime deps)
jSentinel-vaadin -> jSentinel-core
jSentinel-rest -> jSentinel-core
jSentinel-standalone -> jSentinel-core
jSentinel-test -> jSentinel-core (test fixtures)
jSentinel-processor -> proxybuilder (compile-time only)
jSentinel-persistence-testkit -> jSentinel-core (test scope)
jSentinel-persistence-eclipsestore -> jSentinel-core, org.eclipse.store:storage-embedded
demo-* -> their adapter + jSentinel-corejSentinel-core has no Vaadin, Servlet, REST-framework, or storage
dependencies. None of the adapters depend on each other. Only
jSentinel-persistence-eclipsestore carries a third-party storage
dependency — the persistence SPIs themselves live dependency-free in
the core. See Persistence.
Package layout
jSentinel-core packages are organised by concern, not by adapter:
com.svenruppert.jsentinel
├── action/ — ActionAuthorizationService, ActionPermission
├── audit/ — JSentinelAuditService + 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 + SessionDecisionEach adapter adds one small wiring package on top:
com.svenruppert.jsentinel.standalone — Secured, StandaloneLoginFlow,
ThreadLocalSubjectStore (3 classes)
com.svenruppert.jsentinel.logout.vaadin — VaadinLogoutService + Gateway
com.svenruppert.jsentinel.session.vaadin— SessionLifetimeListener
com.svenruppert.jsentinel.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:
| Type | Module | Purpose |
|---|---|---|
AuthorizationDecision | jSentinel-core | Adapter-neutral: Granted / Unauthenticated / Forbidden |
AccessDecision | jSentinel-core | Vaadin-oriented (legacy, kept for backward compatibility) |
Adapters map these to framework-specific behavior:
jSentinel-vaadin→ navigation: continue, reroute to login, or reroute to error.jSentinel-rest→ HTTP status:200/handler,401, or403.
In addition, several sealed decision hierarchies cover the production-hardening SPIs:
LoginAttemptDecision = Allowed | LockedOut(Duration, int)— see Brute-Force ProtectionSessionDecision = Continue | RequireLogin | Invalidate(String reason, String loginRoute)— see Session PolicySessionPolicyDecision = Active | IdleTimeout | AbsoluteLifetimeExceeded— pure-query result ofSessionPolicy.evaluate(SessionMetadata)PolicyDecision = Allowed | Denied | StepUpRequired— see Policy APIJSentinelVersionStatus = Current(at) | Drifted(snapshot, current)andEnforcementOutcome = Continue | SessionStale(status)— see Multi-TenancyRateLimitDecision = Allowed | Throttled(eventsInWindow, limit, window, retryAfter)
JSentinelServiceResolver — one resolver for all SPIs
JSentinelAuditService audit = JSentinelServiceResolver.auditService();
LogoutService logout = JSentinelServiceResolver.logoutService();
LoginAttemptPolicy bruteFor = JSentinelServiceResolver.loginAttemptPolicy();
SessionPolicy<MyUser> session = JSentinelServiceResolver.sessionPolicy();
ActionAuthorizationService<MyUser> action = JSentinelServiceResolver.actionService();
PasswordHasher hasher = JSentinelServiceResolver.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
JSentinelAnnotationScanner scans classes, methods, or any
AnnotatedElement for restriction annotations meta-annotated with
@JSentinelAnnotation. Both adapters use the same scanner.
Generic annotations (in jSentinel-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/andsecurity/code is free ofjava.net.http.*,URI,HttpClient, JSON, or endpoint paths. The contract isDemoBackendClient;HttpDemoBackendClientis the only class with transport knowledge. - REST server is authoritative. Mutating clicks call the server.
200 / 401 / 403decides. LocalPermissionGuardchecks against the cached subject are UX hints only. - Bootstrap goes over REST. The Vaadin
/setupview callsPOST /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
| Type | Module / package | Purpose |
|---|---|---|
JSentinelServiceResolver | jSentinel-core/.../authorization/api | Central SPI cache for all eight services. |
PermissionGuard, AccessDeniedException | jSentinel-core/.../authorization/api | Stateless hasPermission / requirePermission (and role variants). |
AuthenticationService<T,U> | jSentinel-core/.../authentication | SPI: credential validation + subject loading. |
PasswordHasher, PasswordHash, Pbkdf2PasswordHasher | jSentinel-core/.../authentication | Hash + verify + needsRehash drift detection. Demos rehash transparently on login. |
LogoutService, LogoutScope, SubjectId, SubjectSessionRegistry, InMemorySubjectSessionRegistry, LogoutListener, SubjectClearingLogoutService | jSentinel-core/.../logout | Multi-session logout. See Logout Flows. |
VaadinLogoutService | jSentinel-vaadin | Registers as a LogoutListener; invalidates Vaadin + HTTP sessions, redirects browser. |
LoginAttemptPolicy, LoginAttemptDecision, InMemoryLoginAttemptPolicy, LoginAttemptConfiguration[Loader] | jSentinel-core/.../bruteforce | Pluggable login throttling. See Brute-Force Protection. |
SessionPolicy<U>, SessionDecision, SessionMetadata, TimeoutSessionPolicy | jSentinel-core/.../session | Idle / absolute lifetime + session-id rotation. See Session Policy. |
JSentinelAuditService, sealed AuditEvent (27 record types), RingBufferAuditSink, LoggingAuditSink, CompositeAuditService, DefaultCompositeAuditService, StoreBackedSecurityAuditService | jSentinel-core/.../audit | Typed publish/query pipeline. Powers the Vaadin /audit route and the REST GET /api/audit endpoint. See Security Audit. |
ActionAuthorizationService<U>, ActionPermission, StaticActionAuthorizationService | jSentinel-core/.../action | Stable SPI for isAllowed/requireAllowed with auto-audit on denial. |
StaticRolePermissionMapping, RolePermissionResolver | …/authorization/api/permissions | Immutable role → permissions map; permission merge across roles. |
SecuredOperationDescriptor, SecuredOperationRegistry, OperationVisibilityService | …/authorization/api/operations | Generic operation discovery with subject-aware filtering. |
BootstrapConfigurationLoader, BootstrapStatus | jSentinel-core/.../bootstrap | Centralised sysprop+env+default loading; leak-safe status snapshot. |
RestHeaders, BearerTokenExtractor | jSentinel-rest | Case-insensitive header lookup and Bearer-token parsing. |
RestAuthenticationFilter, RestAuthorizationFilter | jSentinel-rest | 401-only and full 401/403 filters. The authorization filter additionally consults SessionPolicy.evaluate(...) when subject metadata is available. |
BodyRestRequest | jSentinel-rest | Body-capable RestRequest. Avoids concrete-class casts. |
BootstrapRestStatusMapper | jSentinel-rest | InitialAdminCreationResult → HTTP status + stable error code. |
SecuredProxy.wrap(Class<T>, T), SecuredProxy.requireAllowed(Class<?>, String) | jSentinel-standalone | Dynamic-proxy enforcement of @RequiresRole / @RequiresPermission on any interface — no framework needed. See Standalone Integration. |
StandaloneLoginFlow<T,U>, LoginResult<U>, ThreadLocalSubjectStore | jSentinel-standalone | Login lifecycle for CLI / desktop / batch apps; integrates with the same brute-force + audit SPIs as the framework adapters. |
Quality — Mutation Coverage
The eight core/adapter and opt-in credential/DX modules below were last
re-measured reactor-wide in 00.75.20 (a touched-module PIT pass — no
module regressed against its prior baseline). The Report column links
to each module’s full site-native breakdown; those reports were rendered
from an earlier snapshot (pre-00.73 security-* module ids), so the
headline number here is the current one while the per-mutator /
survivor detail behind the link predates the rebrand.
| Module | 00.60.00 | 00.70.00 | latest (00.75.20) | Report |
|---|---|---|---|---|
jSentinel-core | 79 % | 86 % | 86 % (2076 / 2413) | report |
jSentinel-vaadin | 90 % | 79 % ¹ | 80 % (249 / 313) | report |
jSentinel-rest | 95 % | 95 % | 95 % (95 / 100) | report |
jSentinel-standalone | 98 % | 97 % | 98 % (44 / 45) | report |
jSentinel-processor | — | 82 % | 78 % ² (54 / 69) | report |
jSentinel-persistence-eclipsestore | — | 70 % | 73 % (265 / 361) | report |
Credential and developer-experience modules, same 00.75.20 pass:
| Module | Added | latest (00.75.20) | Report |
|---|---|---|---|
jSentinel-crypto-bc | 00.71 | 62 % (124 / 199) | report |
jSentinel-credentials-hibp | 00.71 | 57 % (43 / 76) | report |
jSentinel-dx | 00.72 | 71 % (313 / 441) | — |
jSentinel-dx-vaadin | 00.72 | 59 % (44 / 75) | — |
jSentinel-dx-rest | 00.72 | 71 % (48 / 68) | — |
jSentinel-dx-standalone | 00.72 | 60 % (36 / 60) | — |
jSentinel-vaadin-starter | 00.72 | 35 % (56 / 160) | — |
jSentinel-autoservice-processor | 00.72 | 53 % (37 / 70) | — |
The identity-federation stack (00.75–00.79), each at its own most recent measurement — see the linked release notes for full mutator context:
| Module | Added | latest | Notes |
|---|---|---|---|
jSentinel-events | 00.75.00 | 85 % (379 / 445) | Re-measured 00.75.20. |
jSentinel-events-rest | 00.75.00 | 67 % (101 / 151) | Re-measured 00.75.20. |
jSentinel-jwt | 00.76.00 | 75 % (135 / 180) | Re-measured 00.79.20 after JWE additions. |
jSentinel-oauth2 | 00.77.00 | 65 % (210 / 324) | Re-measured 00.79.20 after mTLS/PAR/JAR additions. |
jSentinel-identity-oidc | 00.78.00 | 72 % (170 / 235) | First PIT pass. |
jSentinel-dpop | 00.79.10 | 63 % (57 / 91) | First PIT pass. |
¹ jSentinel-vaadin dipped at 00.70 because the Phase-4c enforcer listener
and the Phase-8 secured components (SecuredButton, SecuredRouterLink,
SecuredMenuItem, SessionManagementView) landed. The gap was 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) and has kept climbing since (249 as of 00.75.20).
² jSentinel-processor keeps a high line-coverage figure even where the
mutation kill rate trails; the module has grown across releases as
diagnostics/wrapper-index code landed. The report
lists the historical survivors (mostly BooleanFalseReturnValsMutator
guard-return flips) from its earlier snapshot.
jSentinel-test and jSentinel-persistence-testkit are test-support
modules (fixtures / contracts); jSentinel-test reached 90 % (82 / 91)
at its first PIT pass in 00.75.20 despite being fixture-heavy.
jSentinel-autoservice-annotations is annotation-only (nothing to
mutate). Demo modules are not PIT targets.
Reports are generated with Pitest via
./mvnw -P mutation verify.
Stable vs. Experimental API
Stable: role-based access, REST adapter contracts, JSentinelSubject,
AccessContext, AuthorizationDecision, scanner, LogoutService,
LoginAttemptPolicy, SessionPolicy, JSentinelAuditService,
ActionAuthorizationService, PasswordHasher, JSentinelServiceResolver.
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.