Security: full RFC 9700 / OAuth 2.1 + Spring Security parity (audit remediation)#150
Open
ancongui wants to merge 7 commits into
Open
Security: full RFC 9700 / OAuth 2.1 + Spring Security parity (audit remediation)#150ancongui wants to merge 7 commits into
ancongui wants to merge 7 commits into
Conversation
… ROPC, CSRF, encoders, HTTP Basic)
Implements the High-severity findings from the security parity audit. TDD
throughout; full suite (4950 passed) + mypy --strict + ruff all green.
OAuth / token security
- PKCE on by default for the authorization_code login flow; always enforced for
public clients (empty client_secret); config-bindable per registration.
- AuthorizationServer rejects client_credentials scopes the client is not
registered for (INVALID_SCOPE, RFC 6749 §5.2) — closes a privilege-escalation
primitive.
- Composition root refuses the built-in placeholder signing secret and enforces
a >=32-byte HMAC key (RFC 7518 §3.2), only when the signer is actually used;
optional audience -> aud claim on issued tokens.
- IdP cloud adapters (Keycloak/Entra/Cognito) disable the ROPC
grant_type=password flow unless pyfly.idp.allow-password-grant=true
(OAuth 2.1 removal / RFC 9700 §2.4).
Spring Security parity
- DelegatingPasswordEncoder ({id}-prefix + upgrade_encoding) plus Pbkdf2 / Scrypt
/ Argon2 encoders behind the PasswordEncoder port; opt-in delegating bean.
- CSRF secure-by-default (active unless csrf.enabled=false) in cookie-gated mode,
so stateless/token (no-cookie) clients are unaffected.
- HTTP-method-scoped request matchers in the HttpSecurity URL DSL.
- HTTP Basic authentication: UserDetailsService SPI + HttpBasicAuthenticationFilter
+ config-driven auto-configuration.
Deferred (documented): form login, generic LogoutConfigurer, RFC 9207 iss check,
refresh-token reuse detection, RS256/JWKS AS signing, introspection/revocation
endpoints, DPoP/mTLS, AuthenticationManager/PermissionEvaluator/@PreFilter.
…n, asymmetric AS signing + JWKS - OAuth2 client callback validates the RFC 9207 iss param (mismatch aborts; require_iss opt-in) - AuthorizationServer detects refresh-token reuse and revokes the whole rotation family (OAuth 2.1) - AuthorizationServer supports RS256/ES256/PS* signing with a private key + kid; jwks() publishes the public JWK set so a resource server can verify AS-minted tokens
…endpoints + OpaqueTokenIntrospector - AuthorizationServer.introspect() (access JWT + refresh token) and authenticate_client() - AuthorizationServerEndpoints: POST /oauth2/token, /oauth2/introspect, /oauth2/revoke, GET /oauth2/jwks (client-authed via Basic or post) - OpaqueTokenIntrospector: resource-server validation of opaque tokens via an introspection endpoint, shared claim mapping
…eject empty client auth Addresses commit security-review findings on the OAuth2 management endpoints: - Revocation is owner-only (RFC 7009 §2.1) — a client can no longer revoke another client's tokens. - Introspection no longer discloses another client's token; only the owner, or a client marked allow_introspection (resource server), may introspect it. - authenticate_client refuses empty client_id/secret and clients with no configured secret (no empty-credential bypass on /introspect and /revoke). Also adds the DPoP/mTLS validation library (RFC 9449 / RFC 8705): jwk_thumbprint, DPoPProofValidator, certificate_thumbprint, confirm_dpop_binding/confirm_mtls_binding.
…tokens — AS binding + RS enforcement - AuthorizationServer.token() accepts a cnf confirmation; token endpoint binds a DPoP proof (cnf.jkt) - OAuth2ResourceServerFilter: opt-in enforce_sender_constraints accepts Bearer + DPoP schemes and, for cnf-bound tokens, verifies the DPoP proof (htm/htu/iat/jti/ath + jkt) or the mTLS client-cert thumbprint - JWKSTokenValidator.validate_and_context(); resource-server config: enforce-sender-constraints, mtls-cert-header
…filter/@post_filter - authentication.py: Authentication, AuthenticationProvider, ProviderManager, DaoAuthenticationProvider (UserDetailsService + PasswordEncoder, timing-equalised, credential erasure) - permission.py: PermissionEvaluator port; hasPermission(target[, type], perm) dispatches to it (flat-permission fallback) - method_security: @pre_filter / @post_filter collection filtering with filterObject binding
…n auto-config - FormLoginFilter: username/password POST authenticated via ProviderManager, session-id rotation (fixation defense), SecurityContext stored in session; redirect or JSON responses - LogoutFilter: invalidates the session, clears the security context, deletes configured cookies; redirect or 204 - Config-driven FormLoginAutoConfiguration + LogoutAutoConfiguration (pre-hashed users), registered as entry points; shared _users_from_config helper
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.
Implements the complete remediation of the RFC 9700 / OAuth 2.1 / Spring Security parity audit — both the High-severity findings and every deferred item — test-driven throughout. ~5021 tests pass,
mypy --strictclean (691 files),ruffclean.Wave 1 — OAuth security MUSTs (shipped defaults)
client_credentialsscope validation — unregistered scopes rejected (INVALID_SCOPE), closing a privilege-escalation primitive.audclaim.grant_type=passwordrequires explicitallow-password-grant.Wave 2 — Spring core parity
DelegatingPasswordEncoder({id}+ upgrade) + Argon2 / PBKDF2 / SCrypt encoders.UserDetailsServiceSPI.Wave 3 — OAuth server completeness + advanced
issmix-up validation in the callback./oauth2/jwkspublication.AuthorizationServerEndpoints) +OpaqueTokenIntrospector— owner-scoped per RFC 7009 §2.1; empty client-auth rejected.cnfbinding + opt-in resource-server proof-of-possession enforcement.Wave 4 — authn/authz breadth
AuthenticationManager/ProviderManager+DaoAuthenticationProvider(timing-equalised, credential erasure).PermissionEvaluatorSPI wired intohasPermission(target[, type], perm).@pre_filter/@post_filtercollection filtering (filterObject).Intentional behavior changes (hardening)
Fail-fast on placeholder signing secrets · CSRF on by default (cookie-gated; opt out with
pyfly.security.csrf.enabled=false) · PKCE sent by default · ROPC requires opt-in ·client_credentialsrejects unregistered scopes.Test plan
pytest— ~5021 passed, 7 skipped, integration deselected (~230 new security tests)mypy --strict src/pyfly— clean (691 files)ruff check+ruff format --check— cleanRemaining (niche / out of scope)
X.509, run-as / switch-user, dynamic client registration (RFC 7591), PAR (9126) / JAR (9101), and JDBC-backed ACL/user stores.