@@ -29,6 +29,7 @@ public final class Request {
2929 private final Map <String , String > pathParameters ;
3030 private final String rawQuery ;
3131 private final UnaryOperator <String > headerLookup ;
32+ private final Map <String , Object > principals ;
3233 private Map <String , String > queryParamCache ;
3334
3435 /**
@@ -52,13 +53,39 @@ public Request(
5253 Map <String , String > pathParameters ,
5354 String rawQuery ,
5455 UnaryOperator <String > headerLookup ) {
56+ this (body , parsed , bodyMapper , operationId , pathParameters , rawQuery , headerLookup , Map .of ());
57+ }
58+
59+ /**
60+ * Builds a {@code Request} from transport-neutral primitives with an explicit principals map.
61+ *
62+ * @param body raw request body bytes; never {@code null}, may be empty
63+ * @param parsed loose structural view of the body (Map / List / boxed primitive), or {@code null}
64+ * @param bodyMapper {@link TypeMapper} that produced {@code parsed}, used for typed conversion;
65+ * may be {@code null} if there is no body
66+ * @param operationId the OpenAPI {@code operationId} the request was routed to
67+ * @param pathParameters path variables extracted by the router
68+ * @param rawQuery raw (percent-encoded) query string, or {@code null} if absent
69+ * @param headerLookup first-value, case-insensitive header lookup; returns {@code null} if absent
70+ * @param principals principals stashed by the security filter, keyed by scheme name
71+ */
72+ public Request (
73+ byte [] body ,
74+ Object parsed ,
75+ TypeMapper bodyMapper ,
76+ String operationId ,
77+ Map <String , String > pathParameters ,
78+ String rawQuery ,
79+ UnaryOperator <String > headerLookup ,
80+ Map <String , Object > principals ) {
5581 this .body = body ;
5682 this .parsed = parsed ;
5783 this .bodyMapper = bodyMapper ;
5884 this .operationId = operationId ;
5985 this .pathParameters = pathParameters ;
6086 this .rawQuery = rawQuery ;
6187 this .headerLookup = headerLookup ;
88+ this .principals = Map .copyOf (principals );
6289 }
6390
6491 public byte [] bytes () {
@@ -163,6 +190,29 @@ public Optional<String> queryParam(String name) {
163190 return raw == null || raw .isBlank () ? Optional .empty () : Optional .of (raw );
164191 }
165192
193+ /**
194+ * Principals stashed by {@code SecurityFilter}, keyed by securityScheme name. Empty when the
195+ * request had no security requirements or when {@code useExternalAuthentication()} is set.
196+ */
197+ public Map <String , Object > principals () {
198+ return principals ;
199+ }
200+
201+ /** Convenience for the common single-scheme case. */
202+ public Optional <Object > principal (String schemeName ) {
203+ return Optional .ofNullable (principals .get (schemeName ));
204+ }
205+
206+ /**
207+ * Returns a new {@code Request} identical to this one except with the supplied principals. Used
208+ * by {@code SecurityFilter} on success; the returned instance carries the principals through to
209+ * the {@link RequestHandler}.
210+ */
211+ public Request withPrincipals (Map <String , Object > principals ) {
212+ return new Request (
213+ body , parsed , bodyMapper , operationId , pathParameters , rawQuery , headerLookup , principals );
214+ }
215+
166216 private static Map <String , String > parseQuery (String query ) {
167217 if (query == null || query .isBlank ()) {
168218 return Map .of ();
0 commit comments