Skip to content
๐Ÿ‡ช๐Ÿ‡บ 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; Unauthenticated โ†’ 401; Forbidden โ†’ 403. 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.