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

jCustos — Lightweight Security for Core Java, REST and Vaadin.

Pluggable authentication, authorization, and annotation-driven protection — wired through Java SPI, no Spring or Jakarta required.

One-prompt setup — Claude Code skills

jsentinel.eu

Capabilities

Annotation-Driven, Three Adapters

Same @RequiresRole / @RequiresPermission annotations protect Vaadin views (navigation listener), REST handlers (filter), and plain Java services (SecuredProxy.wrap(…) dynamic-proxy). One scanner, three call sites.

Framework-Neutral Core

jCustos-core has no Vaadin, Servlet, or REST-framework dependencies. Adapters map a single decision model to navigation or HTTP status.

Java SPI by Design

Authentication, authorization, audit, action, login-attempt, session-policy, password-hashing, logout — every contract pluggable via META-INF/services/. One JCustosServiceResolver resolves them all.

Hardened First-Run Bootstrap

The first administrator is created via a one-time token. POSIX 0600 file, atomic creation, PBKDF2-HMAC-SHA256, never logged, never echoed.

Granted / 401 / 403 — That's It

AuthorizationDecision collapses to three outcomes. REST adapters map them to status codes; Vaadin maps them to navigation. Error bodies are short and generic.

Four Reference Demos

Standalone Vaadin (demo-vaadin), JDK-only REST server + CLI (demo-rest), two-tier setup (demo-vaadin-rest-client), and a pure Core-Java CLI library (demo-standalone) — one annotation set, four call shapes. Both Vaadin demos ship a role-admin UI, user CRUD, and an audit-log view.

AI-Assisted Integration

Integrate the full stack from a single prompt — twelve Claude Code skills for Vaadin, REST and Standalone (plus persistence, hardening and OIDC layers). The docs ship an llms.txt map, so coding agents read the library too.

Production Hardening

JCustosAuditService with 27 sealed event types and a ring-buffer sink. LoginAttemptPolicy brute-force protection with 429 + Retry-After and lockout UI. SessionPolicy with idle/absolute lifetime and session-id rotation after login.

Method Security, Two Ways

Enforce @RequiresRole / @RequiresPermission / @RequiresPolicy on plain Java methods — a compile-time annotation processor generates <Type>Secured wrappers, or SecuredProxy.wrap(…) at runtime. Both route through one JCustosEnforcer.

Policy API

Beyond roles and permissions: a typesafe policy builder, @RequiresPolicy, and a sealed PolicyDecision (Allowed / Denied / StepUpRequired) with resource-aware, owner-or-admin style checks.

Multi-Tenancy + Active-Session Role Refresh

TenantId scopes every store key. JCustosVersion drift detection refreshes roles for live sessions — revoke a role and the next request is rejected (Vaadin reroute / REST 401), no waiting for expiry.

Store-Agnostic Persistence

11 small store SPIs with in-memory defaults, an Eclipse Store reference backend, and a contract testkit. Swap to durable storage with one dependency — no code change. Redis / JDBC are drop-in.

Credential Hardening + Compliance

A standards-aligned credential stack: Argon2id / bcrypt / scrypt, post-KDF HMAC pepper, breached-password checks (Have I Been Pwned), abuse detection — mapped to OWASP ASVS V2, NIST SP 800-63B and 40 CWE classes.

Multi-Session Logout & Action Audit

LogoutService.logout(SubjectId, LogoutScope) signs out one or every session. ActionAuthorizationService emits an ActionDenied audit event on every denial — no manual instrumentation.

Security Event Bus

New in 00.75 — typed security events in signed (Ed25519) envelopes with replay protection, per-(tenant, producer) ordering, producer policy and a REST/SSE bridge. Audit becomes a consumer; revoke a session and every node can hear it. Restart-safe Eclipse-Store stores.

JWT Validation

New in 00.76 — RFC-conformant JWT validation (jCustos-jwt): a disciplined JWKS client, a mandatory algorithm allow-list with hard algorithm-confusion defence (alg:none & key-confusion rejected), strict iss / aud — wired in three config lines via .jwt(...). JOSE stays isolated to one module.

OAuth2 Login (RP)

New in 00.77 — OAuth2 Relying-Party flows: Authorization-Code + PKCE, private_key_jwt client auth, refresh-token rotation with reuse-detection, introspection, revocation and the Device Grant — via .oauth2(...), across Vaadin / REST / Standalone.

OpenID Connect Login

New in 00.78–00.79 — OIDC Relying-Party: ID-token validation (nonce / azp / at_hash), discovery, UserInfo, claims→subject mapping and acr step-up via .oidc(...). One-line vendor profiles (Keycloak, Entra, Auth0, Okta, Google, GitHub) — and the JWT / OAuth2 / OIDC API is now stable.

Mutation-Tested Library

Line coverage tells you tests executed the code.
Mutation testing changes the bytecode at runtime and asks the harder question: did the tests catch the change?

Line coverage
Code ran during a test
Easy to game
vs.
Mutation coverage
Tests detected a real bug
What you actually want

A surviving mutation is a real gap. Here's where the library stands.

jCustos-core
86%
mutation coverage
2076 of 2413 mutations killed · as of 00.75.20
Mutation
86%
jCustos-rest
95%
mutation coverage
95 of 100 mutations killed · as of 00.75.20
Mutation
95%
jCustos-standalone
98%
mutation coverage
44 of 45 mutations killed · as of 00.75.20
Mutation
98%
jCustos-vaadin
80%
mutation coverage
249 of 313 mutations killed · as of 00.75.20
Mutation
80%
jCustos-processor
78%
mutation coverage
54 of 69 mutations killed · as of 00.75.20
Mutation
78%
jCustos-persistence-eclipsestore
73%
mutation coverage
265 of 361 mutations killed · as of 00.75.20
Mutation
73%
jCustos-crypto-bc
62%
mutation coverage
124 of 199 mutations killed · as of 00.75.20
Mutation
62%
jCustos-credentials-hibp
57%
mutation coverage
43 of 76 mutations killed · as of 00.75.20
Mutation
57%
jCustos-events · federation stack
85%
mutation coverage
379 of 445 mutations killed · as of 00.75.20
Mutation
85%
jCustos-jwt · federation stack
75%
mutation coverage
135 of 180 mutations killed · as of 00.79.20
Mutation
75%
jCustos-oauth2 · federation stack
65%
mutation coverage
210 of 324 mutations killed · as of 00.79.20
Mutation
65%
jCustos-identity-oidc · federation stack
72%
mutation coverage
170 of 235 mutations killed · as of 00.78.00
Mutation
72%
jCustos-dpop · federation stack
63%
mutation coverage
57 of 91 mutations killed · as of 00.79.10
Mutation
63%

Mutation coverage = killed ÷ total mutations. PIT rewrites the compiled bytecode and checks whether the tests catch the change — a far harder bar than line coverage.

  • Core & adapter tiles open a full, site-native report — per-mutator breakdown and the surviving-mutant list.
  • Federation tiles (events, JWT, OAuth2, OIDC, DPoP) link to the release notes that measured them; several are a first PIT pass and still climbing, with a rendered per-module report to follow.

Each tile is labelled with the version of its most recent recorded PIT pass. All mutation reports →