Base application.yml holds the framework defaults this
demo runs with: mail transport, datasource, session/security settings, role/privilege map, and the
Docker Compose integration used by bootRun. Each profile file below overrides a subset of those
values for one scenario (local dev, production, tests, and so on). For the full property reference
(every key the framework recognizes, not just the ones this demo sets), see the framework's
CONFIG.md.
./gradlew bootRun --args='--spring.profiles.active=local'local, dev, prd, and docker-keycloak are base profiles you choose directly, one at a time, the
way the command above chooses local. test is not chosen by hand: ./gradlew test applies it
automatically. playwright-test is meant to be combined with a base profile rather than run alone
(see its row below). mfa, step-up, and registration-guard are opt-in add-ons with no base settings of their
own; combine one with a base profile by listing both, comma-separated, in --spring.profiles.active
(Spring Boot applies later profiles' properties over earlier ones when the same key is set in both).
If you omit --args entirely, bootRun still defaults to local: build.gradle:118-123 sets
SPRING_PROFILES_ACTIVE=local unless you pass a Gradle project property, e.g.
./gradlew bootRun -Pprofiles=local,mfa.
| Profile | File | Purpose | What it overrides | Activate |
|---|---|---|---|---|
local |
application-local.yml-example → application-local.yml (gitignored, you create it) |
Everyday local development | Debug logging, DevTools restart/LiveReload, seed-data loading, example OAuth2 client registrations, sendVerificationEmail: false, allowInitialPasswordSetWithoutStepUp: true |
--spring.profiles.active=local |
dev |
application-dev.yml |
Debug-heavy dev server; also what the Docker demo stack (compose.yaml) runs the app container as |
Debug logging, insecure session cookie, audit.flushOnWrite: true |
--spring.profiles.active=dev |
prd |
application-prd.yml |
Production | Thymeleaf caching on, ddl-auto: validate, env-driven datasource, strict/secure cookies, WARN logging, limited actuator exposure, env-driven WebAuthn RP identity and appUrl, requireCanonicalAppUrl: true, no fallback for the remember-me key |
--spring.profiles.active=prd |
test |
src/test/resources/application-test.properties |
Automated JUnit suite | Per-context isolated H2 database, MFA off, test-only unprotectedURIs. It also sets maxFailedLoginAttempts/lockoutDurationMinutes (lines 16-17), but those aren't the framework's property names (failedLoginAttempts/accountLockoutDuration), so they don't bind; lockout stays at the inherited default (10 attempts / 30 min) |
Applied automatically by ./gradlew test |
playwright-test |
application-playwright-test.yml |
Playwright E2E runs; enables the Test API (TestDataController, TestApiSecurityConfig, localhost-only) |
Disables verification/reset emails, points spring.datasource.* at the same local MariaDB the local profile uses, pins appUrl to http://localhost:8080, allowInitialPasswordSetWithoutStepUp: true, MFA off, and restates user.security.unprotectedURIs in full (a list property is replaced wholesale, not merged, so this copy has to match the base list in application.yml:160) |
Combine with a base profile, e.g. local,playwright-test (see TESTING.md) |
docker-keycloak |
application-docker-keycloak.yml (tracked; holds only ${...} placeholders, nothing to copy) |
OIDC login against the bundled Keycloak stack; see keycloak/README.md and AUTHENTICATION.md#keycloak for the full walkthrough |
Adds the Keycloak OAuth2 client/provider from DS_SPRING_USER_KEYCLOAK_* env vars (deliberately no issuer-uri), insecure session cookie |
--spring.profiles.active=docker-keycloak, normally set for you as SPRING_PROFILES_ACTIVE inside docker-compose-keycloak.yml |
mfa |
application-mfa.yml |
Add-on: require PASSWORD + WEBAUTHN | user.mfa.enabled: true (base application.yml:126 has it false); once enabled, the framework auto-unprotects the configured MFA entry-point URIs at runtime, including the challenge page, so a partially-authenticated user can reach them; the profile's yml additionally adds the passkey enrollment endpoints /webauthn/register/options and /webauthn/register to unprotectedURIs (line 25) so that user can register their first passkey; allowInitialPasswordSetWithoutStepUp: true |
Combine with a base profile, e.g. local,mfa |
step-up |
application-step-up.yml |
Add-on: require a recent passkey assertion for credential-altering operations on passkey-only accounts (SUF-02) | user.security.stepUp.enabled: true (base default is false), ttlSeconds: 120, factors: [WEBAUTHN]; this registers the framework's built-in StepUpService, so POST /user/setPassword and passkey delete/rename return 401 until a fresh WEBAUTHN factor exists, and passkey enrollment is gated on a recent authentication. See AUTHENTICATION.md#webauthn-step-up-suf-02 |
Combine with a base profile, e.g. local,step-up |
step-up-e2e |
application-step-up-e2e.yml |
Test-only override for the chromium-step-up Playwright run; never use outside E2E |
Shrinks stepUp.ttlSeconds to 2 (deterministic factor aging), enables user.dev.auto-login-enabled (factorless session via /dev/login-as), and points spring.mail at the Mailpit catcher in compose.dev.yaml (notification assertion). Layer it last, after step-up |
local,playwright-test,step-up,step-up-e2e (see TESTING.md) |
registration-guard |
none (no yml; @Profile("registration-guard") on DomainRegistrationGuard) |
Add-on: domain-restricted registration demo | Activates a RegistrationGuard bean that restricts form/passwordless registration to one email domain (registration.guard.allowed-domain, default @example.com); OAuth2/OIDC registration is unaffected |
Combine with a base profile, e.g. local,registration-guard |
See AUTHENTICATION.md for the mechanics behind mfa
(#mfa), docker-keycloak (#keycloak),
WebAuthn passkeys (#passkeys), step-up
(#webauthn-step-up-suf-02), and registration-guard
(#registration-guard).
- Copy the example file and edit it:
cp src/main/resources/application-local.yml-example src/main/resources/application-local.yml. It is gitignored, so your edits (and any real credentials) never get committed. - This step matters: base
application.ymlleavesuser.registration.sendVerificationEmail: true(application.yml:113) and pointsspring.mail.hostat an SES endpoint with no credentials (application.yml:2-6), so a fresh clone that skips step 1 starts fine but can never send the verification email a new registration needs, and you cannot log in.application-local.yml-examplesetssendVerificationEmail: false(application-local.yml-example:131), so once you copy it, registered accounts are enabled immediately. To exercise the real verification flow instead, set it back totrueand pointspring.mail.*at a real SMTP server. The Docker demo stack (compose.yaml) disables verification email the same way; see Mail. - At minimum, set
spring.mail.username,spring.mail.password, andspring.mail.hostif you want outbound mail to work locally. Set thespring.security.oauth2.client.registration.*client IDs/secrets only if you want to exercise social/Keycloak login. - Docker Compose integration: base
application.ymlsetsspring.docker.compose.file: compose.dev.yaml(lines 66-73), so./gradlew bootRununder any profile starts a MariaDB 12.2 container (springuser/springuser, port 3306) automatically and stops it when the app stops. Setspring.docker.compose.enabled: false(commented hint right below thefile:line) to point at a database you manage yourself instead. - Seed data:
application-local.yml-examplesetsspring.sql.init.mode: alwaysandspring.sql.init.platform: local, plusspring.jpa.defer-datasource-initialization: true(lines 25-30), so every boot under thelocalprofile loadsdata-local.sql(sample events). The script usesINSERT IGNORE, so re-running it on every start is safe.
Required, no fallback:
| Variable | Meaning |
|---|---|
REMEMBER_ME_KEY |
Signs remember-me tokens. Base application.yml falls back to a random UUID per boot (application.yml:157) so the demo runs without it, but application-prd.yml:57 has no fallback: prd fails to start unless this is set. |
Recognized elsewhere (fall back to a demo default when unset):
| Variable | Meaning |
|---|---|
APP_URL |
Canonical base URL for security email links in prd (application-prd.yml:47, default https://example.com). |
DATABASE_URL, DATABASE_USERNAME, DATABASE_PASSWORD |
Production datasource (application-prd.yml:10-12). |
WEBAUTHN_RP_ID, WEBAUTHN_RP_NAME, WEBAUTHN_ALLOWED_ORIGINS |
WebAuthn relying-party identity in prd (application-prd.yml:41-43). |
DS_SPRING_USER_KEYCLOAK_CLIENT_ID, _CLIENT_SECRET, _PROVIDER_AUTHORIZATION_URI, _PROVIDER_TOKEN_URI, _PROVIDER_USER_INFO_URI, _PROVIDER_JWK_SET_URI |
Keycloak OAuth2 client and provider endpoints for docker-keycloak, consumed in application-docker-keycloak.yml:19-20,35,48-50; supplied by keycloak.env when you run docker-compose-keycloak.yml. There is no _PROVIDER_ISSUER_URI: issuer-uri is deliberately left unset, see keycloak/README.md. |
Any framework property can also be set through Spring's relaxed binding (SCREAMING_SNAKE_CASE of
the dotted key). The Docker demo stack (compose.yaml) does this for the app container:
SPRING_DATASOURCE_URL/_USERNAME/_PASSWORD (→ spring.datasource.*), SPRING_PROFILES_ACTIVE, and
SPRING_MAIL_HOST/_PORT plus the SPRING_MAIL_PROPERTIES_MAIL_SMTP_* keys (→ spring.mail.*). The same
pattern works for any other key, e.g. USER_SECURITY_BCRYPTSTRENGTH for user.security.bcryptStrength or
USER_REGISTRATION_SENDVERIFICATIONEMAIL for user.registration.sendVerificationEmail.
spring.mail.username,spring.mail.password,spring.mail.host,spring.mail.port(application.yml:2-6) configure the SMTP transport used for verification, password-reset, and notification email. The base file'shostis a placeholder SES endpoint; set real credentials in your profile.user.registration.sendVerificationEmail(application.yml:113) controls whether a new account must click a verification link before it can log in.falseenables the account immediately at registration.- Both Docker stacks (
compose.yamlanddocker-compose-keycloak.yml) run amailpitservice (Mailpit) that captures outbound mail instead of delivering it, and serves it as a web inbox on http://localhost:8025. The app container reaches it over plain SMTP withSPRING_MAIL_HOST: mailpitandSPRING_MAIL_PORT: 1025, auth and STARTTLS off. Neither stack overridesuser.registration.sendVerificationEmail, so registration verification and password reset run their real email flows: read the message in the web inbox and click the link. Nothing leaves the machine, and Mailpit keeps no volume, sodocker compose downdiscards the captured mail. The resend-verification page works as ofds-spring-user-framework:5.3.1, which bindsResendVerificationDtoinUserAPI.resendRegistrationTokenso the email-only payload thatresend-verification.jsposts passes validation. Under 5.3.0 the endpoint bound the full registrationUserDtoand rejected every resend with HTTP 400. user.mail.fromAddresssets theFromaddress on outbound mail; it is set per profile (e.g.application-local.yml-example:144), not in the base file.
- Bcrypt strength:
user.security.bcryptStrength: 12(application.yml:148);testHashTime: true(application.yml:149) logs the measured hash time at startup so you can tune it. - Failed-login lockout:
failedLoginAttempts: 10,accountLockoutDuration: 30minutes (application.yml:146-147). - Session timeout:
server.servlet.session.timeout: 30m, withsecureandhttp-onlycookie flags (application.yml:92-96). - Default action / protected surface:
defaultAction: denywith an explicitunprotectedURIsallowlist, plusprotectedURIsanddisableCSRFdURIs(application.yml:150-162). - Remember-me:
rememberMe.enabled: true, signing key fromREMEMBER_ME_KEYwith a random-UUID fallback (application.yml:151-158). - Canonical app URL (
user.security.appUrl): prevents Host-header poisoning of password-reset/verification email links (SUF-01 / CWE-640); when unset, the framework derives the host from the (spoofable) requestHostheader and logs a startup warning. Baseapplication.yml:145setshttp://localhost:8080;prddrives it from${APP_URL:https://example.com}(application-prd.yml:47) and also setsrequireCanonicalAppUrl: true(application-prd.yml:49), soprdfails to start without it;playwright-testpins it explicitly tohttp://localhost:8080(application-playwright-test.yml:32). - Allow initial password set without step-up (
user.security.allowInitialPasswordSetWithoutStepUp): controlsPOST /user/setPassword, which lets a passkey-only account set an initial password. As of the framework's SUF-02 hardening, this endpoint returns403unless aStepUpServicebean exists or this istrue. This demo has noStepUpService, so it sets the flagtrueinlocal(application-local.yml-example:141),mfa(application-mfa.yml:24), andplaywright-test(application-playwright-test.yml:35) to keep the passkey flow usable, and leaves it at its secure defaultfalseinprd(application-prd.yml:50).
For OAuth2/OIDC, WebAuthn passkeys, MFA, and the registration guard, see AUTHENTICATION.md.
user.roles.roles-and-privileges and user.roles.role-hierarchy (application.yml:200-222) define
this demo's ROLE_ADMIN > ROLE_MANAGER > ROLE_USER hierarchy and the privileges behind each of
the demo's event-management and user-management actions; edit them in place if you add roles or
privileges. management.newrelic.metrics.export.api-key / .account-id (application.yml:83-87)
are unset placeholders: leave them blank to skip New Relic, or fill them in per profile to export
metrics.