Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
e6d84fd
docs: design spec for typed user.security @ConfigurationProperties
devondragon Aug 14, 2026
525e179
docs: link tracking issue #355 in design spec
devondragon Aug 14, 2026
a1fae0a
docs: implementation plan for typed user.security @ConfigurationPrope…
devondragon Aug 14, 2026
6a62a0b
feat: add PasswordPolicyConfigProperties bound to user.security.passw…
devondragon Aug 14, 2026
697e40e
feat: add RememberMeConfigProperties bound to user.security.remember-…
devondragon Aug 14, 2026
0d1d77b
test: rename remember-me relaxed-binding test to follow should...When…
devondragon Aug 14, 2026
78199ab
feat: add UserSecurityConfigProperties bound to flat user.security.* …
devondragon Aug 14, 2026
eb87719
fix: populate default URI lists and add JavaDoc to all fields in User…
devondragon Aug 14, 2026
58553bf
feat: register user.security config properties beans; add defaults pa…
devondragon Aug 14, 2026
83ac2e7
test: rename parity tests to should/when convention; add rememberMe p…
devondragon Aug 14, 2026
2ca92a6
refactor: inject PasswordPolicyConfigProperties into password consumers
devondragon Aug 14, 2026
f03d829
refactor: inject user.security config properties into WebSecurityConfig
devondragon Aug 14, 2026
6cadda2
refactor: inject UserSecurityConfigProperties into UserSecurityBeansA…
devondragon Aug 14, 2026
2367b03
refactor: inject UserSecurityConfigProperties into remaining service …
devondragon Aug 14, 2026
2dcc692
refactor: inject UserSecurityConfigProperties into controller/web con…
devondragon Aug 14, 2026
01ded56
chore: generate user.security config metadata; drop hand-maintained e…
devondragon Aug 14, 2026
c48df5b
feat: expose secret-free UserSecurityUriView to templates as ${userSe…
devondragon Aug 14, 2026
fe71957
test: guard uri placeholder/bean parity; docs: typed user.security co…
devondragon Aug 14, 2026
1965c7b
test: rename uri placeholder parity test to follow should...When conv…
devondragon Aug 14, 2026
5e119b5
test: cover user.security URI-list default parity against shipped file
devondragon Aug 14, 2026
5c5f2e7
fix: make user.security misconfiguration loud instead of silent
devondragon Aug 14, 2026
5687e83
docs: document user.security startup guards and constructor changes
devondragon Aug 14, 2026
d36ccbd
docs: move API-surface changes into an explicit Breaking Changes section
devondragon Aug 14, 2026
915219a
fix: restore public LoginAttemptService.getMaxFailedLoginAttempts/get…
devondragon Aug 14, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,24 @@

All notable changes to this project are documented here. This project follows [Semantic Versioning](https://semver.org/) for its own public API; the supported Spring Boot versions are tracked separately (see the README compatibility matrix) and are **not** tied to this library's major version.

## [Unreleased]

### Refactoring
- Internal refactor of `user.security.*` to typed `@ConfigurationProperties`: `UserSecurityConfigProperties` (page/action URIs, URI lists, security scalars), `PasswordPolicyConfigProperties`, and `RememberMeConfigProperties`. Config keys are **unchanged** — no consumer configuration action required.

### Breaking Changes
- For consumers that subclass or directly instantiate framework components only (Spring-injected beans are unaffected): the migrated `user.security.*` consumers now take the typed properties objects in their constructors — `TokenHasher(UserSecurityConfigProperties)` replaces `TokenHasher(String)`, `LoginSuccessService` gained a `UserSecurityConfigProperties` parameter, and the Lombok-generated constructors of `UserAPI`, `UserActionController`, `LoginAttemptService`, `LogoutSuccessService`, `UserEmailService`, `UserService`, `PasswordPolicyService`, `WebSecurityConfig`, and `HtmxAwareAuthenticationEntryPointConfiguration` changed accordingly. See MIGRATION.md.
- `WebSecurityConfig`'s previously `@Data`-generated public URI getters (e.g. `getLoginPageURI()`, `getUnprotectedURIsProperty()`) are removed. They were byproducts of the removed `@Value` fields, returned raw property strings, and had no callers outside the framework; read the values from `UserSecurityConfigProperties` instead.

### Features
- New `${userSecurity}` model attribute exposes the configured page/action URIs to Thymeleaf templates (e.g. `${userSecurity.loginPageUri}`) without SpEL bean access. Registered by default; opt out with `user.security.expose-uris-to-model=false`.
- Startup check: a `user.security.*` URI set with a kebab-case or environment-variable spelling (which the typed configuration accepts but request-mapping placeholders do not) now fails startup with the offending keys named, instead of silently splitting the security configuration from the mapped controllers.
- Startup validation of configuration ranges (bcrypt strength 4–31, password-policy `minLength <= maxLength`, `similarityThreshold` 0–100, non-empty `specialChars` when required) when a Bean Validation implementation is on the classpath.
- Remember-me enabled without a signing key, and `usePersistentTokens=true` without a `PersistentTokenRepository` bean, now log explicit warnings instead of silently skipping/downgrading.

### Fixed
- `user.security.rememberMe.usePersistentTokens` was only honored in its exact camelCase spelling; the kebab-case spelling advertised by the generated configuration metadata (`user.security.remember-me.use-persistent-tokens`) bound the properties bean but never created the persistent-token repository, silently downgrading remember-me to hash-based tokens (which cannot be revoked server-side). The condition now accepts every relaxed spelling.

## [5.2.0] - 2026-08-12

This release completes remember‑me (“stay signed in”) with real cookies, optional persistent tokens, and revocation on admin sign‑out/password change, and adds an optional, fail‑closed CAPTCHA layer (Turnstile adapter and a provider‑neutral SPI) for unauthenticated, email‑sending APIs. It also closes a CAPTCHA path‑matching bypass and expands docs and tests.
Expand Down
6 changes: 6 additions & 0 deletions CONFIG.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,12 @@ user:

## Security Settings

`user.security.*` is bound to a typed `@ConfigurationProperties` class (`UserSecurityConfigProperties`). The **camelCase key spellings shown below are canonical** (e.g. `user.security.loginPageURI`, `user.security.registrationConfirmURI`) — relaxed binding also accepts kebab-case (`user.security.login-page-uri`), but the framework's `@GetMapping`/`@RequestMapping` placeholders resolve the exact camelCase key, so a kebab-only spelling for a URI property would move the security configuration without moving the mapped controller. The framework fails startup with the offending keys named if the two ever diverge, so this cannot happen silently. Stick to camelCase for anything under `user.security.*`.

Range and cross-field checks on these properties (bcrypt strength 4–31, password-policy `minLength <= maxLength`, and similar) are validated at startup when a Bean Validation implementation (e.g. `spring-boot-starter-validation`) is on your classpath; without one they are unenforced.

Page and action URIs configured here are also exposed to Thymeleaf templates as the `${userSecurity}` model attribute (e.g. `${userSecurity.loginPageUri}`), registered on every `@Controller` request. Disable it with `user.security.expose-uris-to-model=false` if you don't use it.

- **Failed Login Attempts (`user.security.failedLoginAttempts`)**: Number of failed login attempts before account lockout. Set to `0` to disable lockout. Applies to the login path and to the authenticated password-change endpoint `POST /user/updatePassword` (a locked account is rejected with `HTTP 423`, a wrong current password counts toward lockout, and a correct one resets the counter).
- **Account Lockout Duration (`user.security.accountLockoutDuration`)**: Duration (in minutes) for account lockout. `0` disables lockout; a negative value (e.g. `-1`) locks the account until an administrator unlocks it.
- **BCrypt Strength (`user.security.bcryptStrength`)**: Adjust the bcrypt strength for password hashing. Default is `12`.
Expand Down
32 changes: 32 additions & 0 deletions MIGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ This guide covers migrating applications using the Spring User Framework between
- [Migration Guide](#migration-guide)
- [Table of Contents](#table-of-contents)
- [Migrating to 5.2.x](#migrating-to-52x)
- [`user.security.*` moved to typed configuration properties (config keys unchanged)](#usersecurity-moved-to-typed-configuration-properties-config-keys-unchanged)
- [Remember-me completed; two constructors gained parameters](#remember-me-completed-two-constructors-gained-parameters)
- [Migrating to 5.0.x](#migrating-to-50x)
- [⚠️ ACTION REQUIRED: Reverse-proxy deployments must configure a canonical app URL](#-action-required-reverse-proxy-deployments-must-configure-a-canonical-app-url)
Expand Down Expand Up @@ -46,6 +47,37 @@ This guide covers migrating applications using the Spring User Framework between

## Migrating to 5.2.x

### `user.security.*` moved to typed configuration properties (config keys unchanged)

`user.security.*` (page/action URIs, URI lists, and security scalars) is now bound to a typed
`@ConfigurationProperties` class (`UserSecurityConfigProperties`) instead of individual `@Value`
fields. **Config keys are unchanged** — nothing to update in `application.yml`/`.properties`.

A new `${userSecurity.*}` template attribute is available (e.g. `${userSecurity.loginPageUri}`,
`${userSecurity.registrationUri}`). If your templates currently read these values via
`${@environment.getProperty('user.security.*')}`, you can switch to `${userSecurity.*}` — and on
Spring Boot 4.1.0+ you **must**, since Thymeleaf 3.1.5 rejects the SpEL bean-access form
(`@environment...`) in restricted (layout-decorated) template contexts.

**New startup checks.** The framework now fails startup (with the offending keys named) when a
`user.security.*` URI is set with a kebab-case or environment-variable spelling that the typed
configuration accepts but the framework's request-mapping placeholders do not — a state that
previously split the security configuration from the mapped controllers silently. Use the camelCase
spellings shown in CONFIG.md. Additionally, when a Bean Validation implementation is on your
classpath, out-of-range values (bcrypt strength outside 4–31, password-policy `minLength >
maxLength`, `similarityThreshold` outside 0–100, `requireSpecial` with empty `specialChars`) now
fail startup as named configuration errors instead of misbehaving at runtime.

**Breaking for direct instantiation/subclassing only** (Spring-injected beans are unaffected): the
`user.security.*` consumers now take the typed properties objects in their constructors —
`TokenHasher(UserSecurityConfigProperties)` replaces `TokenHasher(String)`, `LoginSuccessService`
gained a `UserSecurityConfigProperties` parameter, and the Lombok-generated constructors of
`UserAPI`, `UserActionController`, `LoginAttemptService`, `LogoutSuccessService`,
`UserEmailService`, `UserService`, `PasswordPolicyService`, `WebSecurityConfig`, and
`HtmxAwareAuthenticationEntryPointConfiguration` changed accordingly. Also note the code-level
fallback for `user.security.bcryptStrength` is now 12 (previously a dead `@Value` default of 10 that
the shipped configuration file always overrode with 12 — effective behavior is unchanged).

### Remember-me completed; two constructors gained parameters

Remember-me support is now fully functional (persistent token store, cookie configuration, token
Expand Down
Loading
Loading