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

Method Security

Method security puts the authorization check right at the business method, not only at the route or the REST endpoint. jSentinel offers two paths to the same enforcement — pick per type, mix freely:

PathModuleWorks onWhen
Compile-time processor@Secured → generated <Type>Securedsecurity-processorconcrete classesNo runtime reflection, clean stacktraces, works with final methods via a generated subclass
Runtime dynamic proxySecuredProxy.wrap(Interface, impl)security-standaloneinterfacesNo build-time step, decide at runtime

Both paths call the same SecurityEnforcer, so a permission rule behaves identically regardless of which path expressed it.

Annotations

The same generic annotations used for Vaadin views and REST handlers apply to methods:

public interface LibraryService {

  @RequiresPermission("book:list")
  List<String> listBooks();

  @RequiresRole("ADMIN")
  void removeBook(String title);

  @RequiresPolicy("book.owner-or-librarian")
  void editBook(String title, String patch);
}

New in 00.70: @RequiresAnyPermission({...}) and @RequiresAllPermissions({...}) for multi-permission shortcuts, plus @RequiresPolicy("name") for the Policy API.

Path A — Runtime dynamic proxy

LibraryService library =
    SecuredProxy.wrap(LibraryService.class, new InMemoryLibraryService());

library.listBooks();        // throws AccessDeniedException if subject lacks book:list
library.removeBook("Frank"); // requires role ADMIN

SecuredProxy.wrap(...) returns a java.lang.reflect.Proxy. For each invocation it scans the method for security annotations, resolves the current subject, runs the SecurityEnforcer, then either delegates or throws AccessDeniedException. Unannotated methods pass through unchanged.

For single callbacks or lambdas where an interface isn’t a clean fit:

SecuredProxy.requireAllowed(MyHandler.class, "deleteSelected");
documentService.delete(selectedId);

Path B — Compile-time annotation processor

Add security-processor at provided scope (see Quick Start) and annotate a concrete class with @Secured:

@Secured
public class MemberDirectory {

  @RequiresRole("ADMIN")
  public void deactivate(MemberId id) { ... }
}

At compile time the processor generates a MemberDirectorySecured subclass that inserts SecurityEnforcer.require…(…) ahead of each annotated super.<method>(…):

MemberDirectory dir = new MemberDirectorySecured(realEnforcer, ...);
dir.deactivate(id);   // enforced  generated wrapper checks ADMIN first

The generated wrapper carries @GeneratedByProxyBuilder(...) (RUNTIME-reflectable) and a @DelegatesTo("Owner#method(params)") marker per method. Built on com.svenruppert:proxybuilder:00.11.00.

Compile errors are raised for final classes/methods or private / static methods carrying a security annotation — the processor can’t wrap those, so it fails fast rather than silently skipping the check.

One enforcer, two paths

demo-standalone exercises both side by side: LibraryService via SecuredProxy.wrap(...), MemberDirectory via the processor-generated MemberDirectorySecured. The permission semantics are identical because both funnel through SecurityEnforcer.

Choosing a path

  • Interface already exists, want zero build config → runtime proxy.
  • Concrete class, final methods, want no reflection / clean stacktraces / no per-call proxy overhead → compile-time processor.
  • Mixed → use both; they interoperate through the shared enforcer.