Commit ff45644
feat: add WebAuthn/Passkey authentication support (#256)
* Add WebAuthn4J dependency for passkey support
* Add WebAuthn database schema
* Add WebAuthn config prop class
* add to config.md
* Create DTO and Exception class
* Configure WebAuthn repositories
* Create WebAuthn user entity bridge
* Impl credential query & operations repository
* create api controller, webauthn service and update security config
* Fix Spring Security 7.0.2 WebAuthn API compatibility and add unit tests
* Align WebAuthn with Spring Security 7.0.2 JDBC schema and fix auth principal
- Rewrite schema to match Spring Security's expected table/column names
(user_entities, user_credentials) with custom user_account_id column
- Make WebAuthnUserEntityBridge @primary and implement
PublicKeyCredentialUserEntityRepository to ensure Spring Security
uses our bridge instead of the bare JDBC repository
- Add WebAuthnAuthenticationSuccessHandler to convert WebAuthn principal
from PublicKeyCredentialUserEntity to DSUserDetails after login, so
the rest of the app works identically regardless of login method
- Update credential queries for new schema (BLOB credential_id,
Base64 conversion, hard deletes)
- Remove enabled field from WebAuthnCredentialInfo (hard delete model)
- Update all tests for schema and API changes
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Remove PASSKEY.md planning document
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Migrate WebAuthn repositories from JDBC to JPA
Replace JDBC-based persistence with JPA entities and Spring Data
repositories, eliminating the SQL schema script requirement and
fixing the BLOB/VARCHAR mismatch for credential_id. Hibernate now
manages schema generation, and all database operations go through
Spring Data derived queries instead of raw SQL.
- Add WebAuthnUserEntity and WebAuthnCredential JPA entities
- Add WebAuthnUserEntityRepository and WebAuthnCredentialRepository
- Replace JdbcPublicKeyCredentialUserEntityRepository delegate in
WebAuthnUserEntityBridge with JPA repository
- Replace JdbcUserCredentialRepository with JpaUserCredentialRepository
that converts between Spring Security CredentialRecord types and
JPA entities
- Replace JdbcTemplate queries in WebAuthnCredentialQueryRepository
with JPA repository delegation
- Update WebAuthnUserEntityBridgeTest to mock JPA repos
- Remove webauthn-schema.sql (no longer needed)
* Reduce passkey label max length from 255 to 64 characters
Long labels break the UI layout in consuming apps. 64 characters is
more than sufficient for passkey names like "MacBook Pro" or
"Work YubiKey". Updated validation, entity column, and test.
* Default WebAuthn to disabled, requiring explicit opt-in with database schema
* Return proper HTTP error responses from WebAuthn management endpoints
* Remove commented-out version line from build.gradle
* Add WebAuthn/Passkey documentation to README, CONFIG, and database schema
* fix(test): handle temp directory properly in FileAuditLogQueryServiceTest
Set java.io.tmpdir to the test's temp directory so the service resolves
the expected path when the log file does not yet exist, and restore the
original value in a finally block.
* refactor(webauthn): centralize error handling, conditional beans, and pessimistic locking
Replace inline try/catch blocks in WebAuthnManagementAPI with a
dedicated @RestControllerAdvice that maps exceptions to proper HTTP
status codes (404 for user-not-found, 400 for business errors and
validation failures).
Add @ConditionalOnProperty(name="user.webauthn.enabled") to all
WebAuthn beans so they are only registered when the feature is
explicitly enabled. Migrate WebSecurityConfig from @value fields to the
WebAuthnConfigProperties bean and add default property values to
dsspringuserconfig.properties.
Introduce pessimistic write locking in credential deletion to prevent
TOCTOU races in the last-credential protection check.
* test(webauthn): update unit tests and add integration tests for WebAuthn refactoring
Update WebAuthnManagementAPITest to assert thrown exceptions instead of
HTTP status codes, matching the new @RestControllerAdvice approach. Add
user-not-found test cases for each endpoint.
Update WebAuthnCredentialManagementServiceTest to use the new
lockAndCountCredentials method in deletion tests.
Add WebAuthnFeatureDisabledIntegrationTest verifying that no WebAuthn
beans or endpoint mappings are registered when the feature is off.
Add WebAuthnFeatureEnabledIntegrationTest verifying bean registration,
credential retrieval, validation error responses, and business error
responses via MockMvc.
* docs: note WebAuthn disabled-by-default requirement in README
* fix(webauthn): add exception logging and path variable validation to management API
Add @slf4j logging in WebAuthnManagementAPIAdvice to ensure full
exception details are visible at warn level, preventing silent loss of
wrapped cause details. Add @validated and @SiZe(max=512) constraints on
credential ID path variables to reject arbitrarily large strings early.
Add ConstraintViolationException handler for the new class-level
validation.
* fix(webauthn): improve data integrity and fix N+1 query in credential operations
Guard against orphaned WebAuthn user entities by throwing
IllegalStateException when save() cannot link to an application user.
Add nullable=false to the user JoinColumn and corresponding NOT NULL in
the schema. Extract duplicate ownership validation into a private
findCredentialForUser() helper and add a findByIdWithUser() JOIN FETCH
query to eliminate N+1 selects when verifying credential ownership.
* test(audit): fix parallel safety and add sort order assertion
Remove System.setProperty("java.io.tmpdir") hack that could interfere
with parallel test execution. Use null config path instead to bypass the
tmpdir fallback. Switch sort test to ISO timestamps for reliable
cross-JDK parsing and add containsExactly assertion verifying descending
order.
* fix(webauthn): remove noisy stack traces from expected exception logging
Drop the exception object from log.warn() calls in
WebAuthnManagementAPIAdvice. These are expected business and validation
errors (credential not found, validation failures), not unexpected
system failures, so full stack traces add noise without diagnostic value.
* build(test): reduce test output noise with context-aware verbosity
Make test logging conditional on invocation context: `gradle build`
produces quiet output (pass/fail only), while `gradle test` shows
verbose output with stdout/stderr and full stack traces for debugging.
- Add JVM args to suppress Mockito self-attach and CDS warnings
- Create logback-test.xml to eliminate /opt/app/logs file appender errors
- Reduce test log levels from DEBUG to INFO/WARN in application-test.properties
- Remove redundant System.setProperty for security DEBUG logging
- Apply same conditional logging to registerJdkTestTask
Reduces `gradle build` output from ~7,400 lines to ~1,100 lines.
* chore: bump version to 4.2.0-SNAPSHOT for WebAuthn feature
* docs: add 4.2.0 changelog entry and update WebAuthn documentation
Add comprehensive CHANGELOG entry for the WebAuthn/Passkey feature and
14 PR review fixes. Update README version references to 4.2.0 for
Spring Boot 4+ snippets and add cleanup note to WebAuthn features.
Add automatic deletion cleanup note to CONFIG important notes.
---------
Co-authored-by: Oluwatobi <tobbyzomo221@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>1 parent 9625c95 commit ff45644
34 files changed
Lines changed: 2535 additions & 26 deletions
File tree
- db-scripts
- src
- main
- java/com/digitalsanctuary/spring/user
- api
- dto
- exceptions
- persistence
- model
- repository
- security
- service
- resources/config
- test
- java/com/digitalsanctuary/spring/user
- api
- audit
- persistence/repository
- security
- service
- test/config
- resources
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
1 | 53 | | |
2 | 54 | | |
3 | 55 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
65 | 65 | | |
66 | 66 | | |
67 | 67 | | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
68 | 108 | | |
69 | 109 | | |
70 | 110 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
46 | 46 | | |
47 | 47 | | |
48 | 48 | | |
| 49 | + | |
49 | 50 | | |
50 | 51 | | |
51 | 52 | | |
| |||
76 | 77 | | |
77 | 78 | | |
78 | 79 | | |
| 80 | + | |
79 | 81 | | |
80 | 82 | | |
81 | 83 | | |
| |||
88 | 90 | | |
89 | 91 | | |
90 | 92 | | |
| 93 | + | |
91 | 94 | | |
92 | 95 | | |
93 | 96 | | |
| |||
126 | 129 | | |
127 | 130 | | |
128 | 131 | | |
129 | | - | |
| 132 | + | |
130 | 133 | | |
131 | 134 | | |
132 | 135 | | |
133 | 136 | | |
134 | 137 | | |
135 | | - | |
| 138 | + | |
136 | 139 | | |
137 | 140 | | |
138 | 141 | | |
| |||
203 | 206 | | |
204 | 207 | | |
205 | 208 | | |
206 | | - | |
| 209 | + | |
207 | 210 | | |
208 | 211 | | |
209 | 212 | | |
| |||
595 | 598 | | |
596 | 599 | | |
597 | 600 | | |
| 601 | + | |
| 602 | + | |
| 603 | + | |
| 604 | + | |
| 605 | + | |
| 606 | + | |
| 607 | + | |
| 608 | + | |
| 609 | + | |
| 610 | + | |
| 611 | + | |
| 612 | + | |
| 613 | + | |
| 614 | + | |
| 615 | + | |
| 616 | + | |
| 617 | + | |
| 618 | + | |
| 619 | + | |
| 620 | + | |
| 621 | + | |
| 622 | + | |
| 623 | + | |
| 624 | + | |
| 625 | + | |
| 626 | + | |
| 627 | + | |
| 628 | + | |
| 629 | + | |
| 630 | + | |
| 631 | + | |
| 632 | + | |
| 633 | + | |
| 634 | + | |
| 635 | + | |
| 636 | + | |
| 637 | + | |
| 638 | + | |
| 639 | + | |
| 640 | + | |
| 641 | + | |
| 642 | + | |
| 643 | + | |
| 644 | + | |
| 645 | + | |
| 646 | + | |
| 647 | + | |
598 | 648 | | |
599 | 649 | | |
600 | 650 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
13 | 13 | | |
14 | 14 | | |
15 | 15 | | |
16 | | - | |
17 | 16 | | |
18 | 17 | | |
19 | 18 | | |
| |||
52 | 51 | | |
53 | 52 | | |
54 | 53 | | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
55 | 58 | | |
56 | 59 | | |
57 | 60 | | |
| |||
70 | 73 | | |
71 | 74 | | |
72 | 75 | | |
| 76 | + | |
73 | 77 | | |
74 | 78 | | |
75 | 79 | | |
| |||
102 | 106 | | |
103 | 107 | | |
104 | 108 | | |
| 109 | + | |
105 | 110 | | |
106 | 111 | | |
107 | 112 | | |
108 | 113 | | |
109 | 114 | | |
110 | 115 | | |
111 | 116 | | |
112 | | - | |
113 | | - | |
114 | | - | |
115 | | - | |
116 | | - | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
117 | 130 | | |
118 | 131 | | |
119 | 132 | | |
| |||
136 | 149 | | |
137 | 150 | | |
138 | 151 | | |
| 152 | + | |
139 | 153 | | |
140 | 154 | | |
141 | 155 | | |
142 | 156 | | |
143 | 157 | | |
144 | 158 | | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
145 | 163 | | |
146 | 164 | | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
147 | 171 | | |
148 | 172 | | |
149 | 173 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
92 | 92 | | |
93 | 93 | | |
94 | 94 | | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | | - | |
| 1 | + | |
2 | 2 | | |
3 | 3 | | |
0 commit comments