feat(security): Spring Security parity — Tier 4 OAuth2 ecosystem (v26.6.33)#41
Merged
Merged
Conversation
added 6 commits
June 19, 2026 21:05
Add the Rust analog of Spring's OpaqueTokenIntrospector, the first Tier 4 (OAuth2 ecosystem) piece: - TokenIntrospector trait + RemoteTokenIntrospector: POSTs the token to a configured RFC 7662 introspection endpoint (HTTP Basic client auth, token + token_type_hint form), and on `active: true` maps the response claims to an Authentication (RFC 7662 `username` honored over the OIDC preferred_username/sub fallback). - Implements Verifier, so it is a drop-in alternative to JwksVerifier behind a BearerLayer for the opaque-token resource-server pattern (the AS stays the source of truth; nothing is trusted locally). - Fails closed: transport error, non-2xx, non-JSON, or active:false/absent all reject. 2 unit tests (active mapping incl. scope→authorities; inactive/missing/non-object reject) + an end-to-end integration test against an in-process axum RFC 7662 endpoint (real HTTP round-trip + Verifier drop-in). fmt + clippy clean.
Add the Rust analog of Spring's OAuth2AuthorizedClientManager / OAuth2AuthorizedClientService — the outbound side of OAuth2 (obtaining tokens to call DOWNSTREAM services, vs the inbound login flow): - OAuth2AuthorizedClient: a held token (access + optional refresh + expiry + scopes) for a (registration, principal) pair; is_expired() with clock-skew leeway (None expiry = non-expiring). - OAuth2AuthorizedClientService trait + InMemoryOAuth2AuthorizedClientService: store keyed by (registration_id, principal_name). - OAuth2AuthorizedClientManager: authorize_client_credentials() performs the client-credentials grant (service-to-service), caches the token, and reuses it until within the skew window of expiry; on expiry it refreshes (refresh_token grant) or re-fetches. refresh() drives the refresh-token grant directly. A refresh response that omits a new refresh token retains the existing one (RFC 6749 §6). client_secret_basic auth to the token endpoint; fail-mapped to OAuth2Error. 4 unit tests (token-response parsing incl. expiry/scope/refresh retention, missing-access-token error, is_expired skew/missing-expiry) + 2 end-to-end integration tests against an in-process token endpoint (grant+cache hit-once, expired+refresh). fmt + clippy clean.
Add the Rust analog of Spring's OidcClientInitiatedLogoutSuccessHandler: - ClientRegistration gains end_session_endpoint + post_logout_redirect_uri fields/setters; the keycloak() preset derives the end_session endpoint from the realm issuer. - The OAuth2 login callback now stores the registration_id + raw id_token in the session (new SESSION_KEY_REGISTRATION_ID / SESSION_KEY_ID_TOKEN). - POST /logout: after deregistering + invalidating the local session, when the login provider advertises an end_session_endpoint it redirects the browser there with id_token_hint + post_logout_redirect_uri + client_id (RP-initiated logout), so the session ends at the IdP too; otherwise redirects to "/". - oidc_logout_url() builds the end_session URL (exported, testable). 1 unit test (URL build incl. explicit post-logout override + no-end_session → None) + an end-to-end logout integration test (redirects to end_session with the id_token hint, local session still invalidated); the existing plain-logout test still passes. fmt + clippy clean.
…ta [T4.4]
Mount the existing (previously callable-only) AuthorizationServer as a real
OAuth2 HTTP surface:
- AuthorizationServerRouter::router() mounts:
- POST /oauth2/token — the RFC 6749 token endpoint over the client_credentials
and refresh_token grants, client_secret_post auth. Success -> 200
TokenResponse; failure -> the RFC 6749 §5.2 error envelope
({error, error_description}; 401 for invalid_client, else 400; codes
lowercased to the registered RFC names).
- GET /.well-known/oauth-authorization-server — RFC 8414 Authorization Server
Metadata (issuer, token_endpoint, grant_types_supported,
token_endpoint_auth_methods_supported). No jwks_uri: tokens are HS256-signed
(symmetric), so there is no public verification key to publish.
3 tests via Router oneshot (token issuance for client_credentials incl. scope;
bad-client -> 401 invalid_client; metadata advertises issuer + token endpoint).
Server-side authorization_code grant + PKCE and a client-authenticated
/oauth2/revoke remain a documented follow-up (the AS signs symmetric and has no
authorize endpoint yet). fmt + clippy clean.
Adversarial review of the Tier 4 OAuth2 surface surfaced 9 confirmed findings (all availability/hardening/observability — no auth bypass); fixes: - HTTP timeouts (HIGH): the OAuth2 reqwest clients (introspection, outbound token, JWKS, login) had no timeout, so a slow/hostile endpoint could hang the inbound bearer-verification hot path indefinitely. All now use a shared crate::default_http_client() with connect (5s) + read (10s) timeouts; a timeout maps to a fail-closed error. - Non-expiring token (MEDIUM): a token response (incl. refresh) omitting expires_in produced a cached token with no expiry that was served forever. authorized_client_from_token_response now assumes a bounded fallback lifetime (DEFAULT_FALLBACK_TTL_SECONDS) so the token is re-fetched. - Response-body cap (LOW): introspection + token responses now reject an over-large Content-Length (MAX_OAUTH2_RESPONSE_BYTES) before buffering. - SERVER_ERROR mapping (LOW): the token endpoint maps an internal SERVER_ERROR to HTTP 500 with a generic description instead of 400 + the raw signer error. - redirect() panic (LOW): the login redirect() builds the Location header fallibly (falls back to "/"), so a control char in a misconfigured end_session_endpoint can't panic the handler. - Secret redaction (LOW): ClientRegistration (client_secret) and OAuth2AuthorizedClient (access/refresh tokens) now have manual Debug impls that redact, instead of deriving Debug. Documented (not code-fixed): no single-flight on concurrent authorize/refresh (rotating-refresh lost-update) and token-endpoint error bodies surfaced as status only. All oauth2 lib + integration tests green; fmt + clippy clean.
- Spring Security Parity appendix (EN + ES): mark outbound AuthorizedClientManager, RFC 7662 opaque-token introspection, and RP-initiated logout as supported, the authorization server as partial (token + RFC 8414 metadata; authorization_code grant a follow-up); add an "OAuth2 ecosystem" section; mark the OAuth2-ecosystem tier done in the roadmap. - CHANGELOG: v26.6.33 entry (Tier 4) incl. the review hardening + known limitations. MODULES.md: firefly-security oauth2 description expanded. - Bump workspace + path-dep versions to 26.6.33; refresh Cargo.lock. - Rebuild and republish both editions (PDF + EPUB, EN + ES) in docs/book/dist.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Spring Security parity — Tier 4: the OAuth2 ecosystem
The wider OAuth2 surface beyond the browser login flow. All additive — no
behaviour change to existing code. Adversarially reviewed before release.
Added
RemoteTokenIntrospector(Spring's
OpaqueTokenIntrospector); a drop-inVerifierfor opaque bearertokens, fail-closed.
AuthorizedClientManager) —OAuth2AuthorizedClientManager+OAuth2AuthorizedClientServiceobtain,cache, and auto-refresh access tokens for downstream calls (client-credentials
oidc_logout_url+end_session_endpoint/post_logout_redirect_uri;POST /logoutredirects tothe IdP
end_session_endpointwithid_token_hint(
OidcClientInitiatedLogoutSuccessHandler).AuthorizationServerRoutermountsPOST /oauth2/token(RFC 6749) andGET /.well-known/oauth-authorization-server(RFC 8414 metadata).
Adversarial review fixes (9 confirmed — all availability/hardening, no bypass)
endpoint could hang the bearer-verification hot path).
expires_inis absent (no immortal cached token);response-body size cap;
SERVER_ERROR→ HTTP 500 (no raw signer-error leak);fallible logout
redirect()(no panic); redactingDebugforClientRegistration/OAuth2AuthorizedClientsecrets.Tests & docs
via in-process mock servers — all green;
fmt+clippyclean; full-workspacecargo checkgreen at v26.6.33.(PDF + EPUB). CHANGELOG v26.6.33; MODULES.md.
Version bumped to 26.6.33.