Skip to content

Releases: devondragon/SpringUserFramework

5.0.1

16 Jun 01:46

Choose a tag to compare

Maven Central: com.digitalsanctuary:ds-spring-user-framework:5.0.1


Features

  • WebAuthn credential-management re-authentication now returns distinct HTTP status codes
    • Affects: DELETE /user/webauthn/password, DELETE /user/webauthn/credentials/{id}, PUT /user/webauthn/credentials/{id}/label
    • Status mapping:
      • Missing/blank currentPassword → 400 Bad Request
      • Incorrect currentPassword → 401 Unauthorized
      • Account locked (too many failed attempts) → 423 Locked
    • Implementation details:
      • Added WebAuthnReauthenticationException (401) and WebAuthnAccountLockedException (423), both extend WebAuthnException (400 default).
      • WebAuthnManagementAPI.requireCurrentPasswordIfSet now:
        • Checks lock first; if locked, throws WebAuthnAccountLockedException (423) before any password verification.
        • Throws WebAuthnException (400) when currentPassword is missing/blank; this does not count toward lockout.
        • On wrong password, increments LoginAttemptService, then throws WebAuthnReauthenticationException (401).
        • On success, clears failed-attempt counter via LoginAttemptService.loginSucceeded to mirror login semantics.
      • WebAuthnManagementAPIAdvice maps subtypes to precise statuses; base WebAuthnException remains 400.
    • Client impact:
      • These endpoints were introduced in 5.0.0 and previously returned 400 for all failures; clients can now branch on 400/401/423 to give better UX.
      • If a client already treated any 4xx as “re-auth failed,” no change is required.

Fixes

  • Security: trustedHosts matching is now case-insensitive (RFC 4343)
    • AppUrlResolver normalizes configured user.security.trustedHosts to lower case and lower-cases X-Forwarded-Host during comparison.
    • Prevents legitimate mixed-case hosts (e.g., App.Example.Com) from being ignored on the CWE-640 path, which previously forced a fallback to the container’s server name.
    • Continues to honor only the first entry in a multi-valued X-Forwarded-Host (proxy chains).
  • Model: revert Role.privileges to FetchType.EAGER (User.roles remains LAZY)
    • Addresses LazyInitializationException footgun when calling role.getPrivileges() outside an open transaction/session, for negligible performance gain.
    • Rationale: privileges are small, static reference data; there’s no bulk-load path across many Roles.
    • Performance is preserved: the authentication path still loads User → roles → privileges in one round trip via UserRepository.findWithRolesByEmail using @EntityGraph.
    • Repository Javadoc clarified: the two-level entity graph yields a Cartesian product of roles × privileges that Hibernate de-duplicates via Sets; acceptable for a single user.

Breaking Changes

  • Refined HTTP statuses on WebAuthn credential-management re-authentication failures
    • In 5.0.0, missing/incorrect/locked cases all returned 400; in 5.0.1 they return 400/401/423 respectively.
    • These endpoints are new as of 5.0.0. Most clients won’t break if they handled any 4xx generically; update only if you explicitly expected 400 for all re-auth failures.

Documentation

  • CHANGELOG and MIGRATION updated to document:
    • New WebAuthn re-authentication status codes (400 missing, 401 incorrect, 423 locked) and endpoint list.
    • Role.privileges reverted to EAGER; guidance focuses on laziness only for User.roles.
    • Clarified that the entity-graph load is a “single round trip” (bounded, typically one query).
    • JPA note: @EntityGraph fetch results in a roles × privileges Cartesian product, de-duplicated by Sets; fine for single-user loads, not intended for bulk loads.
    • Tokens (PasswordResetToken, VerificationToken): getUser() is EAGER for detached access; the associated User’s roles remain LAZY—use findWithRolesByEmail when authorities are needed.
  • README updates for 5.0.0 release:
    • Installation snippets bumped to 5.0.0; compatibility table reworked (Spring Boot 4.0.x–4.1.x ↔ framework 5.0.x).
    • Notes that framework versioning is independent of Spring Boot’s major; 5.0.0 is a breaking release with a reverse-proxy configuration requirement (user.security.appUrl or trustedHosts).
    • Spring Boot badge updated to 4.0 | 4.1; spring-retry doc dependency adjusted to 2.0.13.

Testing

  • Added WebAuthnManagementAPIAdviceTest to verify precise exception-to-status mapping:
    • Base WebAuthnException → 400, WebAuthnReauthenticationException → 401, WebAuthnAccountLockedException → 423, WebAuthnUserNotFoundException → 404.
  • Tightened WebAuthn API unit tests to expect specific exception subtypes:
    • Locked accounts now raise WebAuthnAccountLockedException; wrong currentPassword raises WebAuthnReauthenticationException.
    • Verified loginAttemptService integration: wrong password increments failures; success resets.
  • Updated integration tests:
    • Incorrect currentPassword paths now assert 401 (previously 400) for delete/rename credential flows.
  • AppUrlResolverTest
    • New case-insensitivity test to confirm mixed-case allow-list and forwarded host match correctly.
  • Repository tests
    • UserRepositoryEntityGraphTest Javadoc/comments corrected to reflect Role.privileges EAGER and User.roles LAZY; continues to verify that plain findByEmail leaves roles uninitialized.

3.6.0 — Security Maintenance (Spring Boot 3.5 / Java 17)

16 Jun 01:46

Choose a tag to compare

Maven Central: com.digitalsanctuary:ds-spring-user-framework:3.6.0


Security-maintenance release for the Spring Boot 3.5 / Java 17 line. This backports the security-critical
fixes from the 4.4.0 and 5.0.x (Spring Boot 4 / Java 21) releases that apply to code present in the 3.5.x line.
It deliberately does not include the larger architectural changes, new features (WebAuthn, MFA, GDPR service,
token-at-rest hashing), or breaking HTTP-contract changes from those releases. The 3.x line is now in
security-maintenance only
— new development happens on the 5.0.x line (Spring Boot 4.0/4.1, Java 21). Java-21
users should migrate to 5.0.x.

Features

  • None

Fixes

  • Security: Optional canonical app URL to mitigate host-header poisoning (CWE-640)
    • Added new configuration property user.security.appUrl. When set (e.g. https://app.example.com), password-reset and email-verification links are built from this value and the X-Forwarded-Host header is ignored.
    • Implementation details:
      • UserAPI now injects user.security.appUrl and uses a new resolveAppUrl(HttpServletRequest) helper.
      • Trailing slash is stripped; if the property is blank, behavior falls back to the legacy UserUtils.getAppUrl(request).
      • All link creation paths now call resolveAppUrl: registration verification email, forgot-password email, and registration event publishing.
      • Spring configuration metadata updated to document the new property.
    • Default remains unchanged (blank), preserving prior behavior. Recommended to set this property when running behind a reverse proxy to close the poisoning vector.
  • Security: Prevent audit log forging/corruption via CR/LF and delimiter injection
    • FileAuditLogWriter now sanitizes all attacker-influenceable fields (action, status, userId, email, IP, sessionId, message, userAgent, extraData) by replacing CR, LF, and | with spaces before writing the pipe-delimited record.
    • Ensures each record remains exactly one line with 10 fields, preventing newline-based record forging and delimiter-based column shifting.
  • Security: Reject OAuth2/OIDC logins where providers explicitly report unverified emails
    • Google (OAuth2 userinfo): getUserFromGoogleOAuth2User now rejects when email_verified is explicitly false (Boolean or case-insensitive String "false"). Absent claim remains trusted.
    • OIDC providers: rejects when standard email_verified claim is explicitly false.
    • Facebook: getUserFromFacebookOAuth2User now rejects when either email_verified (newer Graph API) or verified (older) claims are explicitly false. Absent claims remain trusted.
    • All three paths throw an OAuth2AuthenticationException with error code email_not_verified and log a warning.
  • Security: Sanitize OAuth2 failure messaging to avoid sensitive data leakage
    • Introduced SanitizingOAuth2AuthenticationFailureHandler; on authentication failure it:
      • Logs full exception details server-side.
      • Stores only a generic, user-safe message in session attribute error.message, or a specific non-sensitive message when the error is email_not_verified.
      • Redirects back to the configured login page.
    • WebSecurityConfig now wires this handler into oauth2Login(). This prevents raw exception messages (which may include account emails or provider details) from reaching the browser.
  • Concurrency: Atomic failed-login increment to close lockout-evasion race
    • New repository method UserRepository.incrementFailedAttempts(email) performs a single JPQL UPDATE u.failedLoginAttempts = u.failedLoginAttempts + 1 where u.email = :email with @Modifying(clearAutomatically = true, flushAutomatically = true).
    • LoginAttemptService.loginFailed now:
      • Uses the atomic increment to avoid lost updates under parallel failures.
      • Re-reads the user, and if the threshold is reached, locks and timestamps the account (idempotent lock).
    • Prevents attackers from evading lockout by generating concurrent failures that previously could lose increments.
  • Security: Reduce secret leakage in logs
    • User.toString() now excludes the password field via @ToString.Exclude (prevents bcrypt hash from appearing in logs).
    • Logs no longer include raw tokens or full principals:
      • Sensitive tokens are logged via a short fingerprint (first 6 chars + ellipsis; short tokens masked as ****).
      • Authentication/principal logs report only usernames/emails or principal names; attribute dumps replaced with attribute key sets.
    • Affects logging in UserActionController, UserVerificationService, DSOAuth2UserService, DSOidcUserService, LoginSuccessService, LogoutSuccessService, UserEmailService, UserService, and JpaAuditingConfig.

Breaking Changes

  • OAuth2 login failure UI messaging is now generic
    • Previously, raw AuthenticationException messages were stored in the session and could be displayed by the UI. Now only a generic error or the non-sensitive “email not verified” message is stored.
    • If your UI or integration logic depended on specific raw exception text, update it to rely on generic messaging or server-side logs for detail.

Refactoring

  • Internal cleanup in LoginAttemptService:
    • Removed the old read-modify-write incrementFailedAttempts(User) method in favor of the repository-level atomic increment.
  • Logging refactors to reduce sensitive output (see Fixes).

Documentation

  • CHANGELOG.md: New 3.6.0 section summarizing backported security fixes and maintenance-only posture for the Spring Boot 3.5 / Java 17 line.
  • README.md:
    • Updated installation coordinates to version 3.6.0.
    • Added a maintenance-only banner directing Java 21/Spring Boot 4 users to the 5.0.x line.
  • CONFIG.md:
    • Documented the new user.security.appUrl property, including guidance on when to enable it and its security implications.
  • gradle.properties: Bumped to 3.6.0-SNAPSHOT for development (release plugin produces 3.6.0).

Testing

  • UserAPIUnitTest: Added “App URL resolution (CWE-640)” tests:
    • Verifies configured appUrl overrides X-Forwarded-Host, trailing slash stripping, and fallback to request-derived URL when blank.
  • UserRepositoryTest: New repository slice tests for incrementFailedAttempts:
    • Confirms row count returns and atomic increments via two successive updates; verifies zero rows for non-existent emails.
  • LoginAttemptServiceTest: Updated and expanded:
    • Asserts atomic increment usage, correct locking at threshold, no lock below threshold, behavior when user missing, and when lockout is disabled.
  • SanitizingOAuth2AuthenticationFailureHandlerTest: Ensures generic/non-leaking messages are stored in session and redirects occur, including the specific mapping for email_not_verified.
  • DSOAuth2UserServiceTest and DSOidcUserServiceTest:
    • Added comprehensive tests covering acceptance (true/absent) and rejection (explicit false) of email_verified claims for Google and OIDC providers.
  • UserToStringTest: Verifies that User.toString() does not include the password hash.

Other Changes

  • Build: Gradle release configuration widened to allow release/* branches
    • build.gradle now sets net.researchgate.release git.requireBranch to main|release/.* so security-maintenance releases off release/* (e.g., release/3.6.0) can run ./gradlew release.
    • Still accepts exactly main.

5.0.0

16 Jun 01:46

Choose a tag to compare

Maven Central: com.digitalsanctuary:ds-spring-user-framework:5.0.0


Major release — contains breaking changes across the Java API, HTTP/response contracts, database schema, required configuration, and bean/auto-configuration structure. Read MIGRATION.md ("Migrating to 5.0.x") before upgrading.

⚠️ ACTION REQUIRED if you run behind a reverse proxy: password-reset and email-verification links are now built from a configured canonical URL and ignore X-Forwarded-Host unless it is explicitly allow-listed. Set user.security.appUrl (recommended) or user.security.trustedHosts, otherwise your reset/verification links will point at the wrong host. See MIGRATION.md.

Features

  • Canonical app URL resolution for security emails

    • Introduced AppUrlResolver and new properties user.security.appUrl and user.security.trustedHosts.
    • Password reset and email verification links are now built from the configured canonical URL. If unset, X-Forwarded-Host is honored only when the first value in the header is on the trusted allow-list; otherwise the server name is used.
    • Forwarded port handling derives the port from X-Forwarded-Port or, if absent, from the forwarded scheme to avoid leaking internal ports. Default ports (80/443) are omitted. Trailing slashes in configured URLs are stripped to prevent double slashes in links.
    • Provided a default AppUrlResolver bean (customizable by consumers).
  • Auto-configuration entry point and toggleable cross-cutting features

    • UserConfiguration is now a proper @autoConfiguration.
    • Added independent opt-out toggles (default true): user.async.enabled, user.retry.enabled, user.scheduling.enabled, user.method-security.enabled.
  • Performance: authority loading without N+1

    • Switched User.roles and Role.privileges to LAZY and added UserRepository.findWithRolesByEmail with @EntityGraph to fetch roles and privileges in a single query on the authentication path (DSUserDetailsService, OAuth2/OIDC paths).
    • Adjusted dependent services (e.g., UserService authWithoutPassword) to avoid touching lazy collections outside a session.

Fixes

  • Anti-enumeration hardening for registration flows

    • /user/registration and /user/registration/passwordless now always return the same generic 200 success-shaped body for both new and already-registered emails.
    • /user/resendRegistrationToken always returns a uniform 200 response, regardless of unknown email or already-verified accounts.
    • Added audit events (Registration/PasswordlessRegistration “Failure”) for guard denials and duplicate-email attempts; no registration event is published in duplicate cases.
  • Require current password for sensitive WebAuthn operations

    • Credential-altering operations (remove password, delete/rename passkey) now require currentPassword when the account has a password set; requests missing/incorrect passwords are rejected with 400.
    • Integrated with lockout: locked accounts are rejected, wrong passwords are reported to LoginAttemptService (count toward lockout), and success resets the counter.
    • For passwordless accounts, currentPassword is not required (documented residual risk remains unchanged).
  • Hardened forwarded header handling in AppUrlResolver (CWE-640)

    • Parses multi-valued X-Forwarded-Host (uses first value), validates X-Forwarded-Proto to http/https only, and sanitizes attacker-controlled values before logging.
  • Validation error handling improvements

    • GlobalValidationExceptionHandler now:
      • Scopes to library controllers only (doesn’t override consumer apps’ error handling).
      • Returns 400 for class-level constraints (e.g., @PasswordMatches) and handles ConstraintViolationException with a structured body.
  • Role/privilege setup robustness

    • Startup setup is idempotent and concurrency-safe for multi-node startup:
      • Unique constraints added to role.name and privilege.name.
      • Insertions run in REQUIRES_NEW, catch unique violations, and re-read existing rows (first-writer-wins).
      • Null/blank privilege names from configuration are now guarded and filtered with warnings.
  • Message bundle registration no longer overrides consumers

    • Replaced hard-coded spring.messages.basename with a MessageSource EnvironmentPostProcessor that appends the library bundle (messages/dsspringusermessages) additively and de-duplicates entries. Consumer keys win on collisions.
  • Minor stability and correctness

    • Stripped trailing slash from configured appUrl to honor “no trailing slash” contract.
    • Marked RolePrivilegeSetupService.alreadySetup volatile to ensure visibility across threads.

Breaking Changes

  • Security email host resolution

    • Reset/verification links no longer trust X-Forwarded-Host by default. Configure user.security.appUrl (recommended) or user.security.trustedHosts; otherwise links may target the internal host.
  • HTTP/response contract changes (anti-enumeration)

    • /user/registration and /user/registration/passwordless for existing emails return a generic 200 success body (previously 409).
    • /user/resendRegistrationToken now always returns a generic 200 success body (previously 409 for already-verified or 500 for unknown email).
    • Clients must stop branching on the old 409/500 codes.
  • Re-authentication requirement for credential changes

    • DELETE /user/webauthn/password now accepts a JSON body {"currentPassword": "..."} and requires it for password-holding accounts.
    • DELETE /user/webauthn/credentials/{id} and PUT /user/webauthn/credentials/{id}/label require currentPassword in the body when the account has a password.
    • Update clients to supply currentPassword where applicable.
  • Database schema changes

    • Added UNIQUE + NOT NULL on password_reset_token.token and verification_token.token.
    • Added UNIQUE + NOT NULL on role.name and privilege.name.
    • Consumers managing schema manually must apply DDL and reconcile duplicates/nulls ahead of time.
  • Bean namespacing to avoid collisions

    • Many library beans now use explicit ds*-prefixed names (e.g., dsUserService, dsMailService, dsUserEmailService, dsUserDetailsService, dsLoginAttemptService, dsSessionInvalidationService, dsPasswordPolicyService, dsAuthorityService, dsRolePrivilegeSetupService, dsMailContentBuilder, dsUserAPI, dsGdprAPI, dsMfaAPI, dsWebAuthnManagementAPI, dsUserActionController, dsUserPageController). By-type injection is unaffected; update any by-name @Qualifier/@Resource/@dependsOn references.
  • Configuration model

    • UserConfiguration is now @autoConfiguration and should not be directly @Import-ed or picked up by consumer @componentscan. Use the new opt-out properties for cross-cutting features as needed.
  • Event payloads no longer carry entities

    • OnRegistrationCompleteEvent, UserPreDeleteEvent, and ConsentChangedEvent now carry ids/scalars (userId, userEmail, etc.) instead of live JPA User entities. Update listeners to use the new accessors and load entities as needed within a transaction.
  • Entity equals/hashCode semantics

    • User, PasswordResetToken, VerificationToken, PasswordHistoryEntry, WebAuthnCredential, and WebAuthnUserEntity now use identity-based (id-only) equality (WebAuthnCredential/WebAuthnUserEntity use their assigned String keys).
    • toString excludes collections/associations and secrets (passwords, tokens, keys).
    • Avoid using unsaved (id == null) entities as Set/Map keys.
  • Package consolidation

    • GlobalValidationExceptionHandler moved from com.digitalsanctuary.spring.user.exception to com.digitalsanctuary.spring.user.exceptions.

Refactoring

  • Event data model refactor to id/DTO-based payloads to eliminate detached-entity hazards in async listeners. Adjusted RegistrationListener and UserEmailService to reload User by id.
  • Replaced @DaTa on entities with explicit id-only equals/hashCode and safer toString (no secret leakage).
  • Moved EnvironmentPostProcessor registration from spring.factories to the modern .imports file.
  • Continued bean namespacing (ds* names) across remaining high-collision services and helpers.

Documentation

  • Migration Guide (MIGRATION.md) expanded for 5.0.x:
    • Reverse-proxy configuration (ACTION REQUIRED).
    • Schema migrations for token and role/privilege uniqueness.
    • Anti-enumeration response changes (including passwordless registration).
    • Re-authentication requirements for credential changes.
    • Lazy-loading guidance and remediation patterns.
    • Entity id-based equality caveats.
    • Event payload changes.
    • Validation advice scope change.
    • Package consolidation.
    • Bean name namespaces.
    • Auto-configuration toggles.
  • CHANGELOG.md updated with all of the above and clarified scope of security hardening.
  • README dependency instructions updated to 5.0.0, with a Spring Boot compatibility note.

Testing

  • Added comprehensive tests:
    • AppUrlResolver: trusted hosts, multi-valued forwarded headers, proto validation, port derivation, IPv6, context path, trailing slash behavior.
    • WebAuthnManagementAPI: currentPassword requirement, lockout integration, success/denial paths; integration tests for delete/rename flows and error messaging.
    • Registration anti-enumeration and guard behavior: uniform responses, event/audit expectations for both form and passwordless endpoints.
    • MessageSourceEnvironmentPostProcessor: additive basename merge and message resolution across consumer and library bundles.
    • Validation advice scope and behavior: ensures only library controllers are targeted; asserts 400 handling for class-level constraints.
    • Role/Privilege setup: concurrency/idempotency behavior and uniqueness enforcement; guards for null/blank privilege names.
    • Repository entity-graph: verifies single-query fetch of roles/privileges, no LazyInitializationException after detach, bounded query count.
    • Entity equality/toString: id-only equality contract, exclusion of secrets from toString.
    • GDPR export: reload with entity-graph to out...
Read more

4.4.0 - Security Hardening

15 Jun 12:41

Choose a tag to compare

Features

  • Token security hardened: verification and password-reset tokens are now hashed at rest (HMAC-SHA-256 when user.security.tokenHashSecret is set; plain SHA-256 otherwise). Dual-read ensures pre-upgrade plaintext tokens still work until expiry. Only one active token per user is kept; generators delete any previous token before issuing a new one. New properties:
    • user.security.tokenHashSecret
    • user.security.passwordResetTokenValidityMinutes
    • user.registration.verificationTokenValidityMinutes
  • Session UX and security during password changes:
    • New default behavior preserves the current session and regenerates its ID after a self‑service password change (or removing a password), invalidating only other sessions. Toggle with user.session.invalidation.keep-current-session-on-password-change (default true). Token-based resets still invalidate all sessions.
  • Extensibility and override model:
    • Security core beans (PasswordEncoder, SessionRegistry, RoleHierarchy, DaoAuthenticationProvider) moved to a dedicated auto-configuration with @ConditionalOnMissingBean so consumers can override them safely.
    • AuditLogWriter and MailService moved to an auto-configuration and guarded by @ConditionalOnMissingBean; MailService created via a bean (still lifecycle-managed), allowing full replacement.
    • SecurityFilterChain is now contributed via auto-configuration and ordered at low precedence; it coexists with additional, differently named consumer chains. Full replacement requires defining a bean named securityFilterChain.
  • MFA improvements:
    • Factor merging enabled using a public-API BeanPostProcessor that sets mfaEnabled=true on authentication-processing filters when user.mfa.enabled=true. This fixes the “second factor replaces first” issue by merging factor authorities across steps.
    • When MFA is enabled, the configured factor entry-point URIs (passwordEntryPointUri and webauthnEntryPointUri) are auto-unprotected to prevent redirect loops.
  • WebAuthn integration polish:
    • Success handler now converts the principal to DSUserDetails and publishes a second InteractiveAuthenticationSuccessEvent with the converted auth so profile listeners run for passkey logins too.
  • Audit logging improvements:
    • Optional file rotation (maxFileSizeMb, maxFiles) and explicit durability tradeoffs documented; configurable flushOnWrite and flushRate; bounded query memory (ring buffer limited by user.audit.maxQueryResults).
    • Log writer sanitizes CR/LF and pipe characters in all fields to prevent log forging and parsing issues.
  • Mail delivery hardening:
    • Dedicated bounded executor (dsMailExecutor) for @async mail to avoid starving the shared pool during SMTP stalls; configurable override by bean name.
    • Mail is now genuinely optional: when no JavaMailSender is available, a single startup warning is logged and send operations are safely no‑ops (email-dependent features degrade gracefully).
  • Session clustering: User, Role, Privilege are now Serializable, enabling distributed/persistent sessions (e.g., Spring Session).

Fixes

  • Token replay under concurrency:
    • Verification and password reset flows now use conditional DELETE-by-token as the atomic single-use guard. Exactly one concurrent caller can consume a token; losers see invalid/expired results. Expired tokens are also cleaned up on validate.
  • GDPR deletion atomicity and event timing:
    • The deletion runs inside a real @transactional boundary (self-proxied call) and publishes UserDeletedEvent only after commit via TransactionSynchronization.afterCommit. Failures during contributor cleanup roll back the entire operation and publish no event.
  • Audit safety and visibility:
    • Sanitize audit fields (strip CR/LF and ‘|’); corrected comments and defaults. Rotation defaults disabled to avoid hiding older events from readers until multi-file reading is implemented.
  • Security filter-chain coexistence:
    • Library filter chain no longer disappears when a consumer adds a second chain. The back-off is now by bean name (securityFilterChain), allowing additional chains to coexist. Regression tests added.
  • OAuth2/OIDC hardening:
    • Enforce account status uniformly: locked/disabled accounts are rejected on OAuth2/OIDC/WebAuthn paths (not just form login).
    • Validate email_verified on Google, OIDC, and Facebook (both email_verified and verified claims); explicitly false rejects; absent claims are trusted.
    • Failure handler no longer logs PII or raw messages; stores only a generic safe message in session; maps email_not_verified to a specific generic message; uses getSession(false) so scanners cannot force session creation.
  • Session security:
    • SessionRegistry is now wired into the filter chain; session invalidation and concurrent session counting work as intended.
    • Programmatic login rotates the session ID to prevent fixation; logout now uses LogoutSuccessService so a Logout audit event is published.
  • Registration robustness:
    • Duplicate registration is serialized using SERIALIZABLE isolation and unique email constraint; race losers receive a UserAlreadyExistException instead of raw DB errors.
    • Internal persist* methods now protected to ensure Spring proxies can override them across packages in Spring Framework 7 (fixes NPEs due to package-private methods not being proxied).
  • Brute-force lockout correctness under load:
    • Failed attempts increment is now an atomic DB UPDATE; prevents race conditions allowing attackers to avoid lockout.
  • Thymeleaf templates and mail defaults:
    • Removed test-only CSRF exemption from default properties; user.mail.fromAddress now defaults to blank with a startup warning if a sender exists but no from address is set.
  • Passay upgrade compatibility:
    • PasswordPolicyService adjusted for Passay 2.0 API and package changes.
  • Authorities correctness:
    • Granted authorities now include role names (e.g., ROLE_ADMIN) in addition to privilege names; hasRole() checks now work correctly.

Breaking Changes

  • SecurityFilterChain override model:
    • Changing the back-off to bean-name based means additional consumer chains will now coexist with the library chain. To fully replace the library chain, define a bean named securityFilterChain. Existing apps that relied on any chain suppressing the library chain may now see both chains active; adjust bean naming or reproduce desired rules as needed.
  • UserEmailService constructor signature:
    • Now requires a TokenHasher. If you subclass UserEmailService, update your constructor to accept and pass the TokenHasher to super().
  • Passay upgrade to 2.0.0:
    • Package relocations (rules under org.passay.rule, character data under org.passay.data), PasswordValidator API changes. If you depend on Passay directly, update imports and ensure you don’t downgrade passay via your BOM.
  • Transactional behavior of user registration/password changes:
    • Password hashing now happens outside DB transactions; DB writes run in short, separate transactions. As a result, registerNewUserAccount, changeUserPassword, and setInitialPassword no longer enlist in caller transactions. Outer transaction rollbacks won’t roll back the registration/password change; adjust flows if you previously depended on the old semantics.
  • Default session handling on password change:
    • New default preserves/regenerates current session and invalidates others. Set user.session.invalidation.keep-current-session-on-password-change=false to restore prior “invalidate everything” behavior.

Refactoring

  • Moved MFA factor-merging BeanPostProcessor into a small, dependency-free MfaFilterMergingConfiguration to avoid early instantiation side-effects; extensive Javadoc added warning that enabling MFA flips mfaEnabled for all AbstractAuthenticationProcessingFilter beans (including consumer-defined filters).
  • Moved unconditional security beans (methodSecurityExpressionHandler, HttpSessionEventPublisher, AuthenticationEventPublisher) into an auto-configuration and guarded with @ConditionalOnMissingBean for safe consumer override; relocated @EnableWebSecurity to the filter-chain auto-configuration.

Documentation

  • MIGRATION.md updated:
    • Name-based SecurityFilterChain replacement guidance.
    • Passay 2.0 migration notes.
    • UserEmailService TokenHasher change.
    • New session handling default and toggle.
    • Notes on hashing outside transactions and implications.
  • CONFIG.md expanded:
    • Audit durability, flush rates, rotation settings, bounded query scope.
    • Mail executor details and override by bean name.
    • JPA auditing opt-out (user.jpa.auditing.enabled) to avoid hijacking consumer auditing setup.
  • PROFILE.md updated with warnings that @scope is not inherited and to use the provided @SessionScopedProfile meta-annotation.

Testing

  • Concurrency and DB-integration:
    • Token replay guard proven on PostgreSQL and MariaDB Testcontainers with two racing threads; exactly one consumption succeeds and token is removed.
    • Duplicate registration serialization verified on PostgreSQL and MariaDB Testcontainers; one success, one UserAlreadyExistException, never two rows.
  • GDPR deletion:
    • After-commit behavior verified: event delivered only after deletion commits; rollback path publishes no event and keeps user.
  • Web security:
    • Authorization matrix tests for defaultAction=deny/allow and fail-closed on invalid values.
    • CSRF filter behavior verified (protected URIs require token; exemptions work).
    • Session invalidation integration test ensures SessionRegistry is populated after real login.
    • Logout audit integration test asserts LogoutSuccessService is wired and publishes a Logout audit event.
  • OAuth2/OIDC and WebAuthn:
    • Failure handler sanitized behavior covered (generic messages, email_not_verified mapping, no forced session).
    • Account-status enforcement tests for OAuth2/OIDC/WebAuthn paths.
    • WebAuthn success publishes converted InteractiveAuthenticationSuccessEvent.
  • User API (re-ena...
Read more

4.3.2

28 Mar 22:41

Choose a tag to compare

Features

  • HTMX-aware AuthenticationEntryPoint for session expiry handling (#294)
    • When HTMX requests (HX-Request: true header) hit an expired session, the framework now returns a 401 JSON response with an HX-Redirect header instead of the default 302 redirect that causes HTMX to swap login page HTML into fragment targets
    • New classes: HtmxAwareAuthenticationEntryPoint, HtmxAwareAuthenticationEntryPointConfiguration
    • WebSecurityConfig now always configures exceptionHandling() with the injected entry point (previously only configured when OAuth2 was enabled)
    • Consumer override: define any AuthenticationEntryPoint bean to replace the default
    • Respects server.servlet.context-path for non-root deployments
    • 100% backward-compatible: non-HTMX browser requests get the same 302 redirect as before

Maintenance

  • Fix Groovy space-assignment deprecation in build.gradle (Gradle 10 compatibility)
  • Suppress removal warning on deprecated method test in UserEmailServiceTest

Maven Central

<dependency>
    <groupId>com.digitalsanctuary</groupId>
    <artifactId>ds-spring-user-framework</artifactId>
    <version>4.3.2</version>
</dependency>

4.3.1

22 Mar 23:02

Choose a tag to compare

Bug Fixes

  • WebAuthn MariaDB compatibility: Use Length.LONG32 for byte[] columns (attestationObject, attestationClientDataJson, publicKey) to fix MariaDB row size limit errors when using inline VARBINARY (#286, #287)
  • OAuth2 DSUserDetails attributes: Populate DSUserDetails.getAttributes() from the OAuth2 provider response, fixing cases where attributes were empty after login (#285)
  • Dependencies: Remove redundant webauthn4j-core direct dependency that was already transitively included

Maintenance

  • Lombok: Bump from 1.18.42 to 1.18.44
  • Tests: Add Testcontainers schema validation tests for MariaDB and PostgreSQL to verify column type mappings

Full Changelog

4.3.0...4.3.1

4.3.0

12 Mar 18:36

Choose a tag to compare

What's New

RegistrationGuard SPI

A new Service Provider Interface for gating user registration across all registration paths (form, passwordless, OAuth2, OIDC). Implement RegistrationGuard to add custom pre-registration logic like invite-only access, domain whitelisting, or rate limiting.

  • RegistrationGuard interface with @FunctionalInterface support for lambda implementations
  • RegistrationContext record providing email, source, and provider name
  • RegistrationDecision record with allow() / deny(reason) factory methods
  • Default permit-all guard auto-configured via @ConditionalOnMissingBean
  • Full documentation in REGISTRATION-GUARD.md

OIDC Service Alignment

Fixed four inconsistencies in DSOidcUserService to match DSOAuth2UserService behavior:

  • Email normalization: Email lookup now uses trim().toLowerCase(Locale.ROOT) before findByEmail(), preventing case-sensitive and locale-dependent mismatches
  • Audit events: New OIDC user registration now publishes an "OIDC Registration Success" audit event
  • Transactional boundaries: Added @Transactional at class level for proper database operation handling
  • Login helper integration: loadUser() now routes through LoginHelperService.userLoginHelper() to update lastActivityDate and run lockout checks

Additional Improvements

  • DSUserDetails remains immutable — OIDC tokens are set via a new LoginHelperService overload rather than mutable setters
  • OAuth2Error now includes the denial reason in the error description field for programmatic access by AuthenticationFailureHandler implementations
  • Audit events are published after save() to prevent false-positive audit records if persistence fails
  • RegistrationDecision.deny() uses String.isBlank() (Java 17+) and a named DEFAULT_DENIAL_REASON constant

Full Changelog

4.2.2...4.3.0

4.2.2

12 Mar 01:09

Choose a tag to compare

What's Changed

  • chore(deps): bump com.webauthn4j:webauthn4j-core from 0.31.0.RELEASE to 0.31.1.RELEASE by @dependabot[bot] in #269
  • chore(deps): bump nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect from 3.4.0 to 4.0.0 by @dependabot[bot] in #270
  • feat: Wrap Spring Security 7 MFA in simple user.mfa.* properties by @devondragon in #272
  • chore(deps): bump gradle-wrapper from 9.3.1 to 9.4.0 by @dependabot[bot] in #273
  • Fix non-portable BLOB columnDefinition in WebAuthnCredential by @devondragon in #275

Full Changelog: 4.2.1...4.2.2

4.2.1 - Passwordless Passkey-Only Accounts

28 Feb 04:19

Choose a tag to compare

What's Changed

New Features

  • feat: add passwordless passkey-only account support (#254) by @devondragon in #267
  • feat(dev): add DevLoginAutoConfiguration for local development

Bug Fixes

  • fix(auth): publish InteractiveAuthenticationSuccessEvent from authWithoutPassword()
  • fix: remove dead code and document passwordless endpoint requirements

Improvements

Dependencies

  • chore(deps): bump org.springframework.boot from 4.0.2 to 4.0.3 (#266)
  • chore(deps): bump com.webauthn4j:webauthn4j-core (#265)

Full Changelog: 4.2.0...4.2.1

4.2.0 - Passkey WebAuthn Support

22 Feb 06:20

Choose a tag to compare

What's Changed

Full Changelog: 4.1.0...4.2.0