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

REST Integration

To secure REST handlers, implement RestSubjectResolver, annotate handlers with generic permission annotations, and run them through RestAuthorizationFilter.

A complete runnable reference lives in demo-rest: a JDK-only HTTP server (com.sun.net.httpserver.HttpServer) and an interactive CLI (java.net.http.HttpClient) demonstrating login, server-side operation filtering, and the 200 / 401 / 403 decision flow.

1. Define project permissions and role mapping

public enum DemoPermission {
  DOCUMENT_READ("document:read"),
  DOCUMENT_DELETE("document:delete");

  private final PermissionName permissionName;
  // ...
}
public final class DemoRolePermissionMapping implements RolePermissionMapping {
  @Override
  public Set<PermissionName> permissionsFor(RoleName role) { /* ... */ }
}

2. Implement RestSubjectResolver

public final class MyRestSubjectResolver implements RestSubjectResolver {

  private static final BearerTokenExtractor BEARER = new BearerTokenExtractor();

  @Override
  public Optional<SecuritySubject> resolveSubject(RestRequest request) {
    return BEARER.extract(request)        // case-insensitive Bearer parser
        .flatMap(myTokenStore::resolve)
        .map(this::toSubject);
  }
}

The library does not enforce a token strategy. BearerTokenExtractor and RestHeaders (case-insensitive header lookup) live in security-rest — no need to roll your own.

3. Annotate handlers

public final class DocumentHandlers {
  @RequiresPermission("document:read")
  public void read(RestRequest request, RestResponse response) { /* ... */ }

  @RequiresPermission("document:delete")
  public void delete(RestRequest request, RestResponse response) { /* ... */ }

  @RequiresPermission("document:create")
  public void create(RestRequest request, RestResponse response) {
    // Pattern-match instead of casting to a concrete adapter request type
    if (request instanceof BodyRestRequest body) {
      String json = body.bodyAsUtf8();
      // ...
    }
  }
}

Use BodyRestRequest (in security-rest) when a handler needs the request body. Adapters supply the raw bytes; helpers decode UTF-8.

4. Wire the filter

RestAuthorizationFilter filter =
    new RestAuthorizationFilter(new MyRestSubjectResolver());

filter.authorizeAndHandle(
    request, response, handlers::delete, handlerMethod);

The filter:

  1. Resolves the subject from the request.
  2. Scans the handler method/class for a security annotation.
  3. Builds an AccessContext with resourceType="rest-endpoint".
  4. Runs the matching AuthorizationEvaluator.
  5. Maps the decision: Granted runs the handler; Unauthenticated401; Forbidden403. Error bodies are short and generic — no internals leak.

5. Authenticated-only endpoints

For endpoints that need any authenticated subject but no specific permission (/me, /logout, …), use RestAuthenticationFilter instead of writing your own subject check:

RestAuthenticationFilter authFilter = new RestAuthenticationFilter(resolver);
authFilter.requireAuthenticated(request, response, handlers::me);
// 401 with body "Unauthorized" if no subject; delegates otherwise

6. (Optional) Operation discovery filtered server-side

demo-rest shows a GET /api/operations endpoint that returns only the operations the current subject is allowed to invoke. Built on SecuredOperationRegistry + OperationVisibilityService from security-core — the same permission model that protects the handlers is used to filter the discovery list. Clients never make local authorization decisions.