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:
- Resolves the subject from the request.
- Scans the handler method/class for a security annotation.
- Builds an
AccessContextwithresourceType="rest-endpoint". - Runs the matching
AuthorizationEvaluator. - Maps the decision:
Grantedruns 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 otherwise6. (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.