Standalone Integration
To secure a Core-Java application — a CLI tool, a desktop app, a
batch job, anything without a Vaadin or REST surface — implement the
same SPI contracts as for the framework adapters, then wrap your
business services with Secured.wrap(…). Every annotated method call
runs through the same SecurityAnnotationScanner that powers the
Vaadin and REST adapters. The reference implementation is in
demo-standalone.
security-standalone adapter is three
classes (Secured, StandaloneLoginFlow, ThreadLocalSubjectStore) on
top of security-core — no third-party dependencies. Released as part
of v00.60 alongside an interactive CLI demo (demo-standalone).When to use it
| Setup | Adapter |
|---|---|
| Vaadin Flow web UI | security-vaadin |
| HTTP server / REST endpoints | security-rest |
| CLI, desktop, batch, embedded — anything not a web request | security-standalone |
The standalone adapter is the right fit when there is no request/response cycle to hook a filter into. It enforces security declaratively at the service-method boundary through a dynamic proxy.
1. Define your service interface with annotations
Just like in the Vaadin and REST cases, annotate methods on an
interface with the generic security annotations from security-core:
public interface LibraryService {
@RequiresPermission("book:list")
List<String> listBooks();
@RequiresPermission("book:borrow")
void borrowBook(String title);
@RequiresPermission("book:add")
void addBook(String title);
@RequiresRole("ADMIN")
void removeBook(String title);
}2. Wrap your implementation with Secured.wrap(…)
LibraryService library =
Secured.wrap(LibraryService.class, new InMemoryLibraryService());
library.listBooks(); // throws AccessDeniedException if subject lacks book:list
library.addBook("Frank"); // throws AccessDeniedException if subject lacks book:addSecured.wrap(…) returns a java.lang.reflect.Proxy that implements
your interface. For each method invocation the proxy:
- Scans the method (and its declaring class) for security annotations.
- Resolves the current subject through
SecurityServiceResolverand the configuredSubjectStore. - Runs the evaluator paired with the annotation.
- Either delegates to the real implementation or throws
AccessDeniedException.
Methods without security annotations pass through unchanged.
3. (Optional) Single-shot check for callbacks
When an interface isn’t a clean fit — a Runnable, a callback, a
lambda — use the static helper instead:
public void onClick() {
Secured.requireAllowed(MyHandler.class, "deleteSelected");
documentService.delete(selectedId);
}It locates the named method on the owning class, reads its security
annotations, and throws AccessDeniedException on denial.
4. Drive login with StandaloneLoginFlow
StandaloneLoginFlow<Credentials, User> flow = new StandaloneLoginFlow<>();
LoginResult<User> result = flow.login(new Credentials(user, pass), user);
switch (result) {
case LoginResult.Success<User>(User subject) -> {
// proceed — the subject is now bound into the SubjectStore
}
case LoginResult.Rejected<User> r ->
System.out.println("Bad credentials.");
case LoginResult.LockedOut<User>(var lockout) ->
System.out.printf("Locked out — try again in %s%n", lockout.retryAfter());
}StandaloneLoginFlow does the same lifecycle as the Vaadin and REST
adapters:
- Asks the registered
LoginAttemptPolicywhether the username is currently throttled. - Delegates to the registered
AuthenticationServicefor credential validation. - On success: loads the subject, binds it into the active
SubjectStore, records the success on the brute-force policy, publishes aLoginSucceededaudit event. - On failure: records the failure on the policy, publishes a
LoginFailedaudit event.
Logout is symmetrical:
flow.logout(); // clears the subject from the SubjectStore + emits LogoutPerformed5. Pick a SubjectStore
For a single-user desktop or CLI application, register
ThreadLocalSubjectStore via META-INF/services/:
# META-INF/services/com.svenruppert.vaadin.security.authorization.api.SubjectStore
com.svenruppert.vaadin.security.standalone.ThreadLocalSubjectStoreIt keeps the current subject in a thread-local map keyed by subject
class. For server-side or multi-tenant setups, provide your own
SubjectStore (request-scoped, session-scoped, …).
Reference: the demo-standalone library CLI
Run the demo with:
mvn -pl :demo-standalone exec:java \
-Dexec.mainClass=com.svenruppert.vaadin.security.demo.standalone.DemoAppIt opens an interactive shell against an in-memory book library:
=== Library CLI ===
Seeded users: admin/admin, librarian/librarian, alice/alice
Username: alice
Password: alice
Welcome, Alice. Roles: [READER]
> list
- Effective Java
- Java Concurrency in Practice
- The Pragmatic Programmer
> add "Clean Code"
Access denied. (alice lacks 'book:add'.)
> logout
> exitThe LibraryService interface, the InMemoryLibraryService impl, and
the role-permission mapping (READER, LIBRARIAN, ADMIN) live
entirely in demo-standalone — the library modules carry no
business-specific code.
Building blocks reference
| Type | Module / package | Purpose |
|---|---|---|
Secured.wrap(Class<T>, T) | security-standalone | Dynamic-proxy wrapper over an interface. Throws AccessDeniedException on denial. |
Secured.requireAllowed(Class<?>, String) | security-standalone | Single-shot annotation check for callbacks/lambdas. |
StandaloneLoginFlow<T, U> | security-standalone | Login + logout lifecycle without UI assumptions; integrates brute-force + audit. |
LoginResult<U> | security-standalone | Sealed Success(U) | Rejected | LockedOut(LockedOut). |
ThreadLocalSubjectStore | security-standalone | Default SubjectStore for single-user JVM apps. |
The adapter pulls in exactly one dependency: security-core. No
Servlet, no HTTP client, no Vaadin.