{"id":31784771,"url":"https://github.com/devondragon/springuserframework","last_synced_at":"2026-02-22T08:05:01.525Z","repository":{"id":42552514,"uuid":"338455988","full_name":"devondragon/SpringUserFramework","owner":"devondragon","description":"Easy User Management Framework/Starter App for Spring. Providing registration, login, logout, and more built on top of Spring Security.","archived":false,"fork":false,"pushed_at":"2025-09-22T16:06:01.000Z","size":1281,"stargazers_count":169,"open_issues_count":5,"forks_count":41,"subscribers_count":7,"default_branch":"main","last_synced_at":"2025-09-22T17:54:22.156Z","etag":null,"topics":["spring","spring-boot","spring-security","springboot","user-management"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/devondragon.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2021-02-12T23:18:23.000Z","updated_at":"2025-09-22T16:05:59.000Z","dependencies_parsed_at":"2023-09-24T06:00:14.776Z","dependency_job_id":"5add5b14-c624-4349-946e-899e2fb5fbb4","html_url":"https://github.com/devondragon/SpringUserFramework","commit_stats":null,"previous_names":[],"tags_count":14,"template":false,"template_full_name":null,"purl":"pkg:github/devondragon/SpringUserFramework","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devondragon%2FSpringUserFramework","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devondragon%2FSpringUserFramework/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devondragon%2FSpringUserFramework/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devondragon%2FSpringUserFramework/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/devondragon","download_url":"https://codeload.github.com/devondragon/SpringUserFramework/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devondragon%2FSpringUserFramework/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":279003727,"owners_count":26083610,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-10T02:00:06.843Z","response_time":62,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["spring","spring-boot","spring-security","springboot","user-management"],"created_at":"2025-10-10T11:20:02.767Z","updated_at":"2026-02-22T08:05:01.516Z","avatar_url":"https://github.com/devondragon.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Spring User Framework\n\n[![Maven Central](https://img.shields.io/maven-central/v/com.digitalsanctuary/ds-spring-user-framework.svg)](https://central.sonatype.com/artifact/com.digitalsanctuary/ds-spring-user-framework)\n[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)\n[![Spring Boot 4](https://img.shields.io/badge/Spring%20Boot-4.0-brightgreen)](https://spring.io/projects/spring-boot)\n[![Spring Boot 3](https://img.shields.io/badge/Spring%20Boot-3.5-blue)](https://spring.io/projects/spring-boot)\n[![Java Version](https://img.shields.io/badge/Java-17%20|%2021%20|%2025-brightgreen)](https://www.oracle.com/java/technologies/downloads/)\n\nA comprehensive Spring Boot User Management Framework that simplifies the implementation of robust user authentication and management features. Built on top of Spring Security, this library provides ready-to-use solutions for user registration, login, account management, and more.\n\nCheck out the [Spring User Framework Demo Application](https://github.com/devondragon/SpringUserFrameworkDemoApp) for a complete example of how to use this library.\n\n\n\n## Table of Contents\n- [Spring User Framework](#spring-user-framework)\n  - [Table of Contents](#table-of-contents)\n  - [Features](#features)\n  - [Installation](#installation)\n    - [Spring Boot 4.0 (Latest)](#spring-boot-40-latest)\n      - [Spring Boot 4.0 Key Changes](#spring-boot-40-key-changes)\n    - [Spring Boot 3.5 (Stable)](#spring-boot-35-stable)\n  - [Migration Guide](#migration-guide)\n  - [Quick Start](#quick-start)\n    - [Prerequisites](#prerequisites)\n    - [Step 1: Add Dependencies](#step-1-add-dependencies)\n    - [Step 2: Database Configuration](#step-2-database-configuration)\n    - [Step 3: JPA Configuration](#step-3-jpa-configuration)\n    - [Step 4: Email Configuration (Optional but Recommended)](#step-4-email-configuration-optional-but-recommended)\n    - [Step 5: Essential Framework Configuration](#step-5-essential-framework-configuration)\n    - [Step 6: Create User Profile Extension (Optional)](#step-6-create-user-profile-extension-optional)\n    - [Step 7: Start Your Application](#step-7-start-your-application)\n    - [Step 8: Test Core Features](#step-8-test-core-features)\n    - [Step 9: Customize Pages (Optional)](#step-9-customize-pages-optional)\n    - [Complete Example Configuration](#complete-example-configuration)\n    - [Next Steps](#next-steps)\n  - [Configuration](#configuration)\n  - [Security Features](#security-features)\n    - [Role-Based Access Control](#role-based-access-control)\n    - [Account Lockout](#account-lockout)\n    - [Audit Logging](#audit-logging)\n  - [User Management](#user-management)\n    - [Registration](#registration)\n    - [Profile Management](#profile-management)\n    - [Admin Password Reset](#admin-password-reset)\n  - [Email Verification](#email-verification)\n  - [Authentication](#authentication)\n    - [Local Authentication](#local-authentication)\n    - [WebAuthn / Passkeys](#webauthn--passkeys)\n    - [OAuth2/SSO](#oauth2sso)\n      - [**SSO OIDC with Keycloak**](#sso-oidc-with-keycloak)\n  - [Extensibility](#extensibility)\n    - [Custom User Profiles](#custom-user-profiles)\n    - [Handling User Account Deletion and Profile Cleanup](#handling-user-account-deletion-and-profile-cleanup)\n      - [Enabling Actual Deletion](#enabling-actual-deletion)\n    - [SSO OAuth2 with Google and Facebook](#sso-oauth2-with-google-and-facebook)\n  - [GDPR Compliance](#gdpr-compliance)\n    - [Enabling GDPR Features](#enabling-gdpr-features)\n    - [Data Export (Right of Access)](#data-export-right-of-access)\n    - [Account Deletion (Right to be Forgotten)](#account-deletion-right-to-be-forgotten)\n    - [Consent Management](#consent-management)\n    - [Extending GDPR Exports](#extending-gdpr-exports)\n    - [GDPR Events](#gdpr-events)\n  - [Examples](#examples)\n  - [Contributing](#contributing)\n  - [Reference Documentation](#reference-documentation)\n  - [License](#license)\n\n## Features\n\n- **User Registration and Authentication**\n  - Registration, with optional email verification.\n  - Login and logout functionality.\n  - Forgot password flow.\n  - Admin-initiated password reset with optional session invalidation.\n  - Database-backed user store using Spring JPA.\n  - SSO support for Google\n  - SSO support for Facebook\n  - SSO support for Keycloak\n  - WebAuthn/Passkey support for passwordless login (biometrics, security keys, device authentication)\n  - Configuration options to control anonymous access, whitelist URIs, and protect specific URIs requiring a logged-in user session.\n  - CSRF protection enabled by default, with example jQuery AJAX calls passing the CSRF token from the Thymeleaf page context.\n  - Audit event framework for recording and logging security events, customizable to store audit events in a database or publish them via a REST API.\n  - Role and Privilege setup service to define roles, associated privileges, and role inheritance hierarchy using `application.yml`.\n  - Configurable Account Lockout after too many failed login attempts\n\n- **Advanced Security**\n  - Role and privilege-based authorization\n  - Configurable password policies\n  - Account lockout after failed login attempts\n  - Audit logging for security events\n  - CSRF protection out of the box\n  - WebAuthn/Passkey credential management (register, rename, delete)\n\n- **Extensible Architecture**\n  - Easily extend user profiles with custom data\n  - Override default behaviors where needed\n  - Integration with Spring ecosystem\n  - Customizable UI templates\n\n- **Developer-Friendly**\n  - Minimal boilerplate code to get started\n  - Configuration-driven features\n  - Comprehensive documentation\n  - Demo application for reference\n  - Built-in dev login controller for quick user switching during local development\n\n- **GDPR Compliance** (opt-in)\n  - Data export (Right of Access - Article 15)\n  - Account deletion (Right to be Forgotten - Article 17)\n  - Consent tracking and management (Article 7)\n  - Extensible data contributor system for custom data\n  - Audit trail for all GDPR operations\n\n## Installation\n\nChoose the version that matches your Spring Boot version:\n\n| Spring Boot Version | Framework Version | Java Version | Spring Security |\n| ------------------- | ----------------- | ------------ | --------------- |\n| 4.0.x               | 4.0.x             | 21+          | 7.x             |\n| 3.5.x               | 3.5.x             | 17+          | 6.x             |\n\n### Spring Boot 4.0 (Latest)\n\nSpring Boot 4.0 brings significant changes including Spring Security 7 and requires Java 21.\n\n**Maven:**\n```xml\n\u003cdependency\u003e\n    \u003cgroupId\u003ecom.digitalsanctuary\u003c/groupId\u003e\n    \u003cartifactId\u003eds-spring-user-framework\u003c/artifactId\u003e\n    \u003cversion\u003e4.2.0\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\n**Gradle:**\n```groovy\nimplementation 'com.digitalsanctuary:ds-spring-user-framework:4.2.0'\n```\n\n#### Spring Boot 4.0 Key Changes\n\nWhen upgrading to Spring Boot 4.0, be aware of these important changes:\n\n- **Java 21 Required**: Spring Boot 4.0 requires Java 21 or higher\n- **Spring Security 7**: Includes breaking changes from Spring Security 6.x\n  - All URL patterns in security configuration must start with `/`\n  - Some deprecated APIs have been removed\n- **Jackson 3**: JSON processing uses Jackson 3.x with some API changes\n- **Modular Test Infrastructure**: Test annotations have moved to new packages:\n  - `@AutoConfigureMockMvc` → `org.springframework.boot.webmvc.test.autoconfigure`\n  - `@DataJpaTest` → `org.springframework.boot.data.jpa.test.autoconfigure`\n  - `@WebMvcTest` → `org.springframework.boot.webmvc.test.autoconfigure`\n\nFor testing, you may need these additional dependencies:\n```groovy\ntestImplementation 'org.springframework.boot:spring-boot-data-jpa-test'\ntestImplementation 'org.springframework.boot:spring-boot-webmvc-test'\ntestImplementation 'org.springframework.boot:spring-boot-starter-security-test'\n```\n\n**Upgrading from 3.x?** See the [Migration Guide](MIGRATION.md) for detailed upgrade instructions.\n\n### Spring Boot 3.5 (Stable)\n\nFor projects using Spring Boot 3.5.x with Java 17+:\n\n**Maven:**\n```xml\n\u003cdependency\u003e\n    \u003cgroupId\u003ecom.digitalsanctuary\u003c/groupId\u003e\n    \u003cartifactId\u003eds-spring-user-framework\u003c/artifactId\u003e\n    \u003cversion\u003e3.5.1\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\n**Gradle:**\n```groovy\nimplementation 'com.digitalsanctuary:ds-spring-user-framework:3.5.1'\n```\n\n## Migration Guide\n\nIf you're upgrading from a previous version, see the **[Migration Guide](MIGRATION.md)** for:\n\n- Step-by-step upgrade instructions\n- Breaking changes and how to address them\n- Spring Security 7 compatibility requirements\n- Test infrastructure changes\n- Guidance for developers extending the framework\n\n## Quick Start\n\nFollow these steps to get up and running with the Spring User Framework in your application.\n\n### Prerequisites\n\n- **For Spring Boot 4.0**: Java 21 or higher\n- **For Spring Boot 3.5**: Java 17 or higher\n- A database (MariaDB, PostgreSQL, MySQL, H2, etc.)\n- SMTP server for email functionality (optional but recommended)\n\n### Step 1: Add Dependencies\n\n1. **Add the main framework dependency** (see [Installation](#installation) for version selection):\n\n   **Spring Boot 4.0 (Java 21+):**\n   ```groovy\n   implementation 'com.digitalsanctuary:ds-spring-user-framework:4.2.0'\n   ```\n\n   **Spring Boot 3.5 (Java 17+):**\n   ```groovy\n   implementation 'com.digitalsanctuary:ds-spring-user-framework:3.5.1'\n   ```\n\n2. **Add required dependencies**:\n\n   **Important**: This framework requires these additional Spring Boot starters to function properly:\n\n   Maven:\n   ```xml\n   \u003cdependency\u003e\n       \u003cgroupId\u003eorg.springframework.boot\u003c/groupId\u003e\n       \u003cartifactId\u003espring-boot-starter-thymeleaf\u003c/artifactId\u003e\n   \u003c/dependency\u003e\n   \u003cdependency\u003e\n       \u003cgroupId\u003eorg.springframework.boot\u003c/groupId\u003e\n       \u003cartifactId\u003espring-boot-starter-mail\u003c/artifactId\u003e\n   \u003c/dependency\u003e\n   \u003cdependency\u003e\n       \u003cgroupId\u003eorg.springframework.boot\u003c/groupId\u003e\n       \u003cartifactId\u003espring-boot-starter-data-jpa\u003c/artifactId\u003e\n   \u003c/dependency\u003e\n   \u003cdependency\u003e\n       \u003cgroupId\u003eorg.springframework.boot\u003c/groupId\u003e\n       \u003cartifactId\u003espring-boot-starter-security\u003c/artifactId\u003e\n   \u003c/dependency\u003e\n   \u003cdependency\u003e\n       \u003cgroupId\u003eorg.springframework.retry\u003c/groupId\u003e\n       \u003cartifactId\u003espring-retry\u003c/artifactId\u003e\n   \u003c/dependency\u003e\n   ```\n\n   Gradle:\n   ```groovy\n   implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'\n   implementation 'org.springframework.boot:spring-boot-starter-mail'\n   implementation 'org.springframework.boot:spring-boot-starter-data-jpa'\n   implementation 'org.springframework.boot:spring-boot-starter-security'\n   implementation 'org.springframework.retry:spring-retry'\n   ```\n\n### Step 2: Database Configuration\n\nConfigure your database in `application.yml`. The framework supports all databases compatible with Spring Data JPA:\n\n**MariaDB/MySQL:**\n```yaml\nspring:\n  datasource:\n    url: jdbc:mariadb://localhost:3306/yourdb?createDatabaseIfNotExist=true\n    username: dbuser\n    password: dbpassword\n    driver-class-name: org.mariadb.jdbc.Driver\n```\n\n**PostgreSQL:**\n```yaml\nspring:\n  datasource:\n    url: jdbc:postgresql://localhost:5432/yourdb\n    username: dbuser\n    password: dbpassword\n    driver-class-name: org.postgresql.Driver\n```\n\n**H2 (for development/testing):**\n```yaml\nspring:\n  datasource:\n    url: jdbc:h2:mem:testdb\n    driver-class-name: org.h2.Driver\n```\n\n### Step 3: JPA Configuration\n\n```yaml\nspring:\n  jpa:\n    hibernate:\n      ddl-auto: update  # Creates/updates tables automatically\n    show-sql: false     # Set to true for SQL debugging\n```\n\n### Step 4: Email Configuration (Optional but Recommended)\n\nFor password reset and email verification features:\n\n```yaml\nspring:\n  mail:\n    host: smtp.gmail.com       # or your SMTP server\n    port: 587\n    username: your-email@gmail.com\n    password: your-app-password\n    properties:\n      mail:\n        smtp:\n          auth: true\n          starttls:\n            enable: true\n\nuser:\n  mail:\n    fromAddress: noreply@yourdomain.com  # Email \"from\" address\n```\n\n### Step 5: Essential Framework Configuration\n\nAdd these minimal settings to get started:\n\n```yaml\nuser:\n  # Basic security settings\n  security:\n    defaultAction: deny                    # Secure by default\n    bcryptStrength: 12                     # Password hashing strength\n    failedLoginAttempts: 5                 # Account lockout threshold\n    accountLockoutDuration: 15             # Lockout duration in minutes\n\n  # Registration settings\n  registration:\n    sendVerificationEmail: false           # true = email verification required\n                                          # false = auto-enable accounts\n```\n\n### Step 6: Create User Profile Extension (Optional)\n\nIf you need additional user data beyond the built-in fields, create a profile extension:\n\n```java\n@Entity\n@Table(name = \"app_user_profile\")\npublic class AppUserProfile extends BaseUserProfile {\n    private String department;\n    private String phoneNumber;\n    private LocalDate birthDate;\n\n    // Getters and setters\n    public String getDepartment() { return department; }\n    public void setDepartment(String department) { this.department = department; }\n\n    // ... other getters and setters\n}\n```\n\n### Step 7: Start Your Application\n\n1. **Run your Spring Boot application**:\n   ```bash\n   mvn spring-boot:run\n   # or\n   ./gradlew bootRun\n   ```\n\n2. **Verify the framework is working**:\n   - Navigate to `http://localhost:8080/user/login.html` to see the login page\n   - Check your database - user tables should be created automatically\n   - Look for framework startup messages in the console\n\n### Step 8: Test Core Features\n\n**Create your first user:**\n- Navigate to `/user/register.html`\n- Fill out the registration form\n- If `sendVerificationEmail=false`, you can login immediately\n- If `sendVerificationEmail=true`, check your email for verification link\n\n**Test login:**\n- Navigate to `/user/login.html`\n- Use the credentials you just created\n\n### Step 9: Customize Pages (Optional)\n\nThe framework provides default HTML templates, but you can override them:\n\n1. **Create custom templates** in `src/main/resources/templates/user/`:\n   - `login.html` - Login page\n   - `register.html` - Registration page\n   - `forgot-password.html` - Password reset page\n   - And more...\n\n2. **Use your own CSS** by adding stylesheets to `src/main/resources/static/css/`\n\n### Complete Example Configuration\n\nHere's a complete `application.yml` for a typical setup:\n\n```yaml\nspring:\n  datasource:\n    url: jdbc:mariadb://localhost:3306/myapp?createDatabaseIfNotExist=true\n    username: appuser\n    password: apppass\n    driver-class-name: org.mariadb.jdbc.Driver\n\n  jpa:\n    hibernate:\n      ddl-auto: update\n    show-sql: false\n\n  mail:\n    host: smtp.gmail.com\n    port: 587\n    username: myapp@gmail.com\n    password: myapppassword\n    properties:\n      mail:\n        smtp:\n          auth: true\n          starttls:\n            enable: true\n\nuser:\n  mail:\n    fromAddress: noreply@myapp.com\n\n  security:\n    defaultAction: deny\n    bcryptStrength: 12\n    failedLoginAttempts: 3\n    accountLockoutDuration: 30\n\n  registration:\n    sendVerificationEmail: true\n\n  # Optional: Audit logging\n  audit:\n    logEvents: true\n    logFilePath: ./logs/audit.log\n```\n\n### Next Steps\n\n- Read the [Configuration Guide](CONFIG.md) for advanced settings\n- See [Extension Examples](PROFILE.md) for custom user profiles\n- Check out the [Demo Application](https://github.com/devondragon/SpringUserFrameworkDemoApp) for a complete example\n\n## Configuration\n\nThe framework uses a configuration-first approach to customize behavior. See the [Configuration Guide](CONFIG.md) for detailed documentation of all configuration options.\n\nKey configuration categories:\n\n- **Security**: Access control, password policies, CSRF protection\n- **Mail**: Email server settings for verification and notification emails\n- **User Registration**: Self-registration options, verification requirements\n- **Authentication**: Local and OAuth2 provider configuration\n- **UI**: Paths to customized templates and views\n\n## Security Features\n\n### Role-Based Access Control\n\nDefine roles and privileges with hierarchical inheritance:\n\n```yaml\nuser:\n  roles:\n    roles-and-privileges:\n      \"[ROLE_ADMIN]\":\n        - ADMIN_PRIVILEGE\n        - USER_MANAGEMENT_PRIVILEGE\n      \"[ROLE_USER]\":\n        - LOGIN_PRIVILEGE\n        - SELF_SERVICE_PRIVILEGE\n    role-hierarchy:\n      - ROLE_ADMIN \u003e ROLE_USER\n```\n\n### Account Lockout\n\nPrevent brute force attacks with configurable lockout policies:\n\n```yaml\nuser:\n  security:\n    failedLoginAttempts: 5\n    accountLockoutDuration: 30  # minutes\n```\n\n### Audit Logging\n\nTrack security-relevant events with built-in audit logging:\n\n```yaml\nuser:\n  audit:\n    logEvents: true\n    logFilePath: /path/to/audit/log\n    flushOnWrite: false\n    flushRate: 10000\n```\n\n## User Management\n\n### Registration\n\nThe registration flow is configurable and can operate in two modes:\n\n**Auto-Enable Mode** (default: `user.registration.sendVerificationEmail=false`):\n- Form submission validation\n- Email uniqueness check\n- User account is immediately enabled and can login\n- No verification email is sent\n- User has full access immediately after registration\n\n**Email Verification Mode** (`user.registration.sendVerificationEmail=true`):\n- Form submission validation\n- Email uniqueness check\n- User account is created but **disabled**\n- Verification email is sent with confirmation link\n- User must click verification link to enable account\n- Account remains disabled until email verification is completed\n- Configurable initial roles assigned after verification\n\n**Configuration Example:**\n```yaml\nuser:\n  registration:\n    sendVerificationEmail: true  # Enable email verification (default: false)\n```\n\n**Note:** When email verification is disabled, user accounts are immediately active and functional. When enabled, accounts require email confirmation before login is possible.\n\n### Profile Management\n\nUsers can:\n- Update their profile information\n- Change their password\n- Delete their account (configurable to either disable or fully delete)\n\n### Admin Password Reset\n\nAdministrators can trigger password resets for users programmatically:\n\n```java\n@Autowired\nprivate UserEmailService userEmailService;\n\n// Reset password and invalidate all user sessions\nint sessionsInvalidated = userEmailService.initiateAdminPasswordReset(user, appUrl, true);\n\n// Reset password without invalidating sessions\nuserEmailService.initiateAdminPasswordReset(user, appUrl, false);\n\n// Use configured appUrl (from user.admin.appUrl property)\nuserEmailService.initiateAdminPasswordReset(user);\n```\n\n**Features:**\n- Requires `ROLE_ADMIN` authorization (`@PreAuthorize`)\n- Optional session invalidation to force re-authentication\n- Sends password reset email with secure token\n- Comprehensive audit logging with correlation IDs\n- Cryptographically secure tokens (256-bit entropy)\n\n**Configuration:**\n```yaml\nuser:\n  admin:\n    appUrl: https://myapp.com  # Base URL for password reset links\n```\n\n**Security Notes:**\n- Admin identity is derived from `SecurityContext`, not user input\n- Sessions are invalidated *after* email is sent to prevent lockout\n- URL validation prevents XSS (blocks javascript:, data: schemes)\n\n## Email Verification\n\n\n\nThe framework includes a complete email verification system:\n- Token generation and verification\n- Customizable email templates\n- Token expiration and renewal\n- Automatic account activation\n\n\n## Authentication\n\n### Local Authentication\n\nUsername/password authentication with:\n- Secure password hashing (bcrypt)\n- Account lockout protection\n- Remember-me functionality\n\n### WebAuthn / Passkeys\n\nPasswordless authentication using biometrics (Touch ID, Face ID, Windows Hello), security keys (YubiKey), or device-based credentials. Built on Spring Security's WebAuthn support.\n\n**Features:**\n- Passwordless login via platform authenticators and roaming security keys\n- Passkey management REST API (list, rename, delete credentials)\n- Last-credential protection (prevents lockout if user has no password)\n- Synced passkey support (iCloud Keychain, Google Password Manager, etc.)\n- Automatic cleanup of passkey data when a user account is deleted\n- Disabled by default; must be explicitly enabled with required database tables\n\n**Setup:**\n\n1. Enable WebAuthn in your configuration:\n   ```yaml\n   user:\n     webauthn:\n       enabled: true\n       rpId: localhost                           # Your domain in production\n       rpName: My Application\n       allowedOrigins: https://localhost:8443    # Must match browser origin\n   ```\n\n2. Add the WebAuthn authentication endpoints to your unprotected URIs:\n   ```yaml\n   user:\n     security:\n       unprotectedURIs: ...,/webauthn/authenticate/**,/login/webauthn\n   ```\n\n3. Create the required database tables. If using `ddl-auto: update`, Hibernate will create them automatically. Otherwise, apply the schema manually (see `db-scripts/mariadb-schema.sql`).\n\n**Requirements:**\n- HTTPS is required in production (HTTP works on `localhost` for development)\n- Users must be authenticated before registering a passkey\n- See the [Configuration Guide](CONFIG.md) for all WebAuthn settings\n\n**Management API Endpoints** (all require authentication):\n\n| Endpoint | Method | Description |\n|----------|--------|-------------|\n| `/user/webauthn/credentials` | GET | List user's passkeys |\n| `/user/webauthn/has-credentials` | GET | Check if user has passkeys |\n| `/user/webauthn/credentials/{id}/label` | PUT | Rename a passkey (max 64 chars) |\n| `/user/webauthn/credentials/{id}` | DELETE | Delete a passkey |\n\n### OAuth2/SSO\n\nSupport for social login providers:\n- Google\n- Facebook\n- Apple\n- Keycloak\n- Custom providers\n\nConfiguration example:\n\n```yaml\nspring:\n  security:\n    oauth2:\n      client:\n        registration:\n          google:\n            client-id: YOUR_GOOGLE_CLIENT_ID\n            client-secret: YOUR_GOOGLE_CLIENT_SECRET\n            redirect-uri: \"{baseUrl}/login/oauth2/code/google\"\n          facebook:\n            client-id: YOUR_FACEBOOK_CLIENT_ID\n            client-secret: YOUR_FACEBOOK_CLIENT_SECRET\n            redirect-uri: \"{baseUrl}/login/oauth2/code/facebook\"\n          keycloak:\n            client-id: YOUR_KEYCLOAK_CLIENT_ID\n            client-secret: YOUR_KEYCLOAK_CLIENT_SECRET\n            redirect-uri: \"{baseUrl}/login/oauth2/code/keycloak\"\n\n```\nFor public OAuth you will need a public hostname and HTTPS enabled.  You can use ngrok or Cloudflare tunnels to create a public hostname and tunnel to your local machine during development.  You can then use the ngrok hostname in your Google, Facebook and Keycloak developer console configuration.\n\n\n#### **SSO OIDC with Keycloak**\nTo enable SSO:\n1. Create OIDC client in Keycloak admin console.\n2. Update your `application-docker-keycloak.yml`:\n   ```yaml\n   spring:\n     security:\n       oauth2:\n         client:\n            registration:\n              keycloak:\n                client-id: ${DS_SPRING_USER_KEYCLOAK_CLIENT_ID} # Keycloak client ID for OAuth2\n                client-secret: ${DS_SPRING_USER_KEYCLOAK_CLIENT_SECRET} # Keycloak client secret for OAuth2\n                authorization-grant-type: authorization_code # Authorization grant type for OAuth2\n                scope:\n                  - email # Request email scope for OAuth2\n                  - profile # Request profile scope for OAuth2\n                  - openid # Request oidc scope for OAuth2\n                client-name: Keycloak # Name of the OAuth2 client\n                provider: keycloak\n            provider:\n              keycloak: # https://www.keycloak.org/securing-apps/oidc-layers\n                issuer-uri: ${DS_SPRING_USER_KEYCLOAK_PROVIDER_ISSUER_URI}\n                authorization-uri: ${DS_SPRING_USER_KEYCLOAK_PROVIDER_AUTHORIZATION_URI}\n                token-uri: ${DS_SPRING_USER_KEYCLOAK_PROVIDER_TOKEN_URI}\n                user-info-uri: ${DS_SPRING_USER_KEYCLOAK_PROVIDER_USER_INFO_URI}\n                user-name-attribute: preferred_username # https://www.keycloak.org/docs-api/latest/rest-api/index.html#UserRepresentation\n                jwk-set-uri: ${DS_SPRING_USER_KEYCLOAK_PROVIDER_JWK_SET_URI}\n   ```\n\n### Dev Login (Local Development)\n\nThe framework includes a built-in dev login controller that lets developers quickly switch between user accounts without entering passwords. This eliminates the need for consuming applications to write boilerplate dev-login controllers.\n\n**Setup:**\n\n1. Activate the `local` Spring profile (e.g., `spring.profiles.active=local`)\n2. Enable dev login in your configuration:\n\n```yaml\n# application-local.yml\nuser:\n  dev:\n    auto-login-enabled: true\n    login-redirect-url: /dashboard  # optional, defaults to /\n```\n\n**Endpoints** (only available when enabled with the `local` profile):\n\n| Endpoint | Method | Description |\n|----------|--------|-------------|\n| `/dev/login-as/{email}` | GET | Authenticate as the specified user and redirect |\n| `/dev/users` | GET | List all enabled user emails (JSON) |\n\n**Example usage during development:**\n- Visit `http://localhost:8080/dev/users` to see available accounts\n- Visit `http://localhost:8080/dev/login-as/admin@example.com` to instantly log in as that user\n\n**Security:** This feature requires **both** `user.dev.auto-login-enabled=true` **and** the `local` Spring profile to be active. A prominent warning banner is logged on startup when dev login is active. **Never enable this in production.**\n\nSee the [Configuration Guide](CONFIG.md) for all dev login settings.\n\n## Extensibility\n\nThe framework is designed to be extended without modifying the core code.\n\n### Custom User Profiles\n\nExtend the `BaseUserProfile` to add your application-specific user data:\n\n```java\n@Service\npublic class CustomUserProfileService implements UserProfileService\u003cCustomUserProfile\u003e {\n    @Override\n    public CustomUserProfile getOrCreateProfile(User user) {\n        // Implementation\n    }\n\n    @Override\n    public CustomUserProfile updateProfile(CustomUserProfile profile) {\n        // Implementation\n    }\n}\n```\nRead more in the [Profile Guide](PROFILE.md).\n\n### Handling User Account Deletion and Profile Cleanup\nBy default, when a user account is \"deleted\" through the framework's services or APIs, the account is marked as disabled (`enabled=false`) rather than being physically removed from the database. This is controlled by the `user.actuallyDeleteAccount` configuration property, which defaults to `false`.\n\n#### Enabling Actual Deletion\n\nIf you require user accounts to be physically deleted from the database, set the following property in your `application.properties` or `application.yml`:\n\n```properties\nuser.actuallyDeleteAccount=true\n```\n\nCleaning Up Related Data (e.g., User Profiles)\nWhen user.actuallyDeleteAccount is set to true, the framework needs a way to ensure that related data, such as application-specific user profiles extending BaseUserProfile, is also cleaned up to avoid orphaned data or foreign key constraint violations.\n\nTo facilitate this in a decoupled manner, the framework publishes a UserPreDeleteEvent immediately before the User entity is deleted from the database. This event is published within the same transaction as the user deletion.\n\nConsuming applications that have extended BaseUserProfile (or have other user-related data) should listen for this event and perform the necessary cleanup operations.\n\nEvent Class: com.digitalsanctuary.spring.user.event.UserPreDeleteEvent Event Data: Contains the User entity that is about to be deleted (event.getUser()).\n\nExample Event Listener:\n\nHere's an example of how a consuming application can implement an event listener to delete its specific user profile (DemoUserProfile in this case) when a user is deleted:\n\n```java\npackage com.digitalsanctuary.spring.demo.user.profile;\n\nimport org.springframework.context.event.EventListener;\nimport org.springframework.stereotype.Component;\nimport org.springframework.transaction.annotation.Transactional;\nimport lombok.RequiredArgsConstructor;\nimport lombok.extern.slf4j.Slf4j;\nimport com.digitalsanctuary.spring.user.event.UserPreDeleteEvent;\n\n/**\n * Listener for user profile deletion events. This class listens for UserPreDeleteEvent and deletes the associated DemoUserProfile. It is assumed that\n * the DemoUserProfile is mapped to the User entity with a one-to-one relationship.\n */\n@Component\n@RequiredArgsConstructor\n@Slf4j\npublic class UserProfileDeletionListener {\n    private final DemoUserProfileRepository demoUserProfileRepository;\n    // Inject other repositories if needed (e.g., EventRegistrationRepository)\n\n    @EventListener\n    @Transactional // Joins the transaction started by UserService.deleteUserAccount\n    public void handleUserPreDelete(UserPreDeleteEvent event) {\n        Long userId = event.getUser().getId();\n        log.info(\"Received UserPreDeleteEvent for userId: {}. Deleting associated DemoUserProfile...\", userId);\n\n        // Option 1: Delete profile directly (if no further cascades needed from profile)\n        // Since DemoUserProfile uses @MapsId, its ID is the same as the User's ID\n        demoUserProfileRepository.findById(userId).ifPresent(profile -\u003e {\n            log.debug(\"Found DemoUserProfile for userId: {}. Deleting...\", userId);\n            // If DemoUserProfile itself has relationships needing cleanup (like EventRegistrations)\n            // that aren't handled by CascadeType.REMOVE or orphanRemoval=true,\n            // handle them here *before* deleting the profile.\n            // Example: eventRegistrationRepository.deleteByUserProfile(profile);\n            demoUserProfileRepository.delete(profile);\n            log.debug(\"DemoUserProfile deleted for userId: {}\", userId);\n        });\n\n        // Option 2: If DemoUserProfile has CascadeType.REMOVE/orphanRemoval=true\n        // on its collections (like eventRegistrations), deleting the profile might be enough.\n        // demoUserProfileRepository.deleteById(userId);\n\n        log.info(\"Finished processing UserPreDeleteEvent for userId: {}\", userId);\n    }\n}\n```\n\nBy implementing such a listener, your application ensures data integrity when the actual user account deletion feature is enabled, without requiring the core framework library to have knowledge of your specific profile entities. If you leave user.actuallyDeleteAccount as false, this event is not published, and no listener implementation is required for profile cleanup\n\n\n\n### SSO OAuth2 with Google and Facebook\nThe framework supports SSO OAuth2 with Google, Facebook and Keycloak.  To enable this you need to configure the client id and secret for each provider.  This is done in the application.yml (or application.properties) file using the [Spring Security OAuth2 properties](https://docs.spring.io/spring-security/reference/servlet/oauth2/login/core.html). You can see the example configuration in the Demo Project's `application.yml` file.\n\n\n## GDPR Compliance\n\nThe framework provides opt-in GDPR compliance features to help your application meet European data protection requirements. These features are **disabled by default** and must be explicitly enabled.\n\n### Enabling GDPR Features\n\nAdd the following to your `application.yml`:\n\n```yaml\nuser:\n  gdpr:\n    enabled: true                    # Master toggle for all GDPR features\n    exportBeforeDeletion: true       # Automatically export data before deletion\n    consentTracking: true            # Enable consent grant/withdrawal tracking\n```\n\nWhen enabled, the following REST endpoints become available (all require authentication):\n\n| Endpoint                    | Method | Description                        |\n| --------------------------- | ------ | ---------------------------------- |\n| `/user/gdpr/export`         | GET    | Export all user data as JSON       |\n| `/user/gdpr/delete`         | POST   | Request account deletion           |\n| `/user/gdpr/consent`        | POST   | Record consent grant or withdrawal |\n| `/user/gdpr/consent/status` | GET    | Get current consent status         |\n\n### Data Export (Right of Access)\n\nUsers can request a complete export of their data via the `/user/gdpr/export` endpoint. The export includes:\n\n- **User account data**: Name, email, registration date, roles\n- **Audit history**: Login events, password changes, profile updates\n- **Consent records**: All consent grants and withdrawals with timestamps\n- **Token metadata**: Verification and password reset token expiry (not actual tokens)\n- **Custom data**: Any data contributed by registered `GdprDataContributor` beans\n\n**Example Response:**\n```json\n{\n  \"success\": true,\n  \"data\": {\n    \"exportedAt\": \"2024-01-15T10:30:00Z\",\n    \"user\": {\n      \"id\": 123,\n      \"email\": \"user@example.com\",\n      \"firstName\": \"John\",\n      \"lastName\": \"Doe\",\n      \"createdDate\": \"2023-06-01T08:00:00Z\",\n      \"roles\": [\"ROLE_USER\"]\n    },\n    \"auditHistory\": [...],\n    \"consents\": [...],\n    \"customData\": {}\n  }\n}\n```\n\n### Account Deletion (Right to be Forgotten)\n\nUsers can request complete deletion of their account via the `/user/gdpr/delete` endpoint. The deletion process:\n\n1. **Exports data** (if `exportBeforeDeletion=true`) and includes it in the response\n2. **Notifies contributors** via `GdprDataContributor.prepareForDeletion()`\n3. **Publishes `UserPreDeleteEvent`** for custom cleanup listeners\n4. **Deletes framework data**: Verification tokens, password reset tokens, password history\n5. **Deletes user entity** from database\n6. **Publishes `UserDeletedEvent`** for post-deletion processing\n7. **Invalidates all sessions** across all devices\n8. **Logs out** the current session\n\n**Important**: This performs a hard delete. Ensure you have the `UserPreDeleteEvent` listener configured (see [Handling User Account Deletion](#handling-user-account-deletion-and-profile-cleanup)) to clean up related data.\n\n### Consent Management\n\nTrack user consent for various purposes (marketing, analytics, data processing, etc.):\n\n**Recording Consent:**\n```bash\n# Grant consent\ncurl -X POST /user/gdpr/consent \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"consentType\": \"MARKETING\", \"granted\": true}'\n\n# Withdraw consent\ncurl -X POST /user/gdpr/consent \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"consentType\": \"MARKETING\", \"granted\": false}'\n\n# Custom consent type\ncurl -X POST /user/gdpr/consent \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"consentType\": \"CUSTOM\", \"customType\": \"newsletter_weekly\", \"granted\": true}'\n```\n\n**Built-in Consent Types:**\n- `MARKETING` - Marketing communications\n- `ANALYTICS` - Analytics and tracking\n- `THIRD_PARTY` - Third-party data sharing\n- `PROFILING` - User profiling\n- `CUSTOM` - Application-specific (requires `customType` field)\n\n**Checking Consent Status:**\n```bash\ncurl /user/gdpr/consent/status?type=MARKETING\n```\n\nAll consent changes are recorded in the audit log with timestamps, IP addresses, and user agent information.\n\n### Extending GDPR Exports\n\nTo include your application's custom data in GDPR exports, implement the `GdprDataContributor` interface:\n\n```java\n@Component\npublic class OrderDataContributor implements GdprDataContributor {\n\n    private final OrderRepository orderRepository;\n\n    public OrderDataContributor(OrderRepository orderRepository) {\n        this.orderRepository = orderRepository;\n    }\n\n    @Override\n    public String getDataKey() {\n        return \"orders\";  // Key in the export JSON\n    }\n\n    @Override\n    public Object contributeData(User user) {\n        // Return data to include in export (will be serialized to JSON)\n        return orderRepository.findByUserId(user.getId())\n            .stream()\n            .map(this::toExportDto)\n            .toList();\n    }\n\n    @Override\n    public void prepareForDeletion(User user) {\n        // Clean up data before user deletion (runs within transaction)\n        // WARNING: Only delete LOCAL database data here, not external APIs\n        orderRepository.deleteByUserId(user.getId());\n    }\n\n    private OrderExportDto toExportDto(Order order) {\n        // Map to DTO for export\n    }\n}\n```\n\n**Important**: The `prepareForDeletion()` method runs within the same database transaction as user deletion. Only perform local database operations here. For external API cleanup, use a `UserDeletedEvent` listener instead.\n\n### GDPR Events\n\nThe framework publishes Spring events for GDPR operations:\n\n| Event                   | When Published                        | Use Case                            |\n| ----------------------- | ------------------------------------- | ----------------------------------- |\n| `UserPreDeleteEvent`    | Before user deletion (in transaction) | Clean up related database records   |\n| `UserDeletedEvent`      | After successful deletion             | External API cleanup, notifications |\n| `UserDataExportedEvent` | After data export                     | Audit logging, analytics            |\n| `ConsentChangedEvent`   | After consent grant/withdrawal        | Trigger consent-dependent workflows |\n\n**Example: External Cleanup After Deletion**\n```java\n@Component\npublic class ExternalCleanupListener {\n\n    @EventListener\n    @Async  // Run asynchronously after transaction commits\n    public void onUserDeleted(UserDeletedEvent event) {\n        // Safe to call external APIs here\n        externalCrmService.deleteCustomer(event.getUserEmail());\n        analyticsService.anonymizeUser(event.getUserId());\n    }\n}\n```\n\n## Examples\n\nFor complete working examples, check out the [Spring User Framework Demo Application](https://github.com/devondragon/SpringUserFrameworkDemoApp).\n\n\n## Contributing\n\nWe welcome contributions of all kinds! If you'd like to help improve SpringUserFramework, please read our [Contributing Guide](CONTRIBUTING.md) for details on how to get started, report issues, and submit pull requests. Let's build something great together!\n\n\n## Reference Documentation\n\n- [API Documentation](https://digitalSanctuary.github.io/SpringUserFramework/)\n- [Migration Guide](MIGRATION.md)\n- [Configuration Guide](CONFIG.md)\n- [Security Guide](SECURITY.md)\n- [Customization Guide](CUSTOMIZATION.md)\n- [Demo Application](https://github.com/devondragon/SpringUserFrameworkDemoApp)\n\n## License\n\nThis project is licensed under the Apache License 2.0 - see the [LICENSE](LICENSE) file for details.\n\n---\n\nCreated by [Devon Hillard](https://github.com/devondragon/) at [Digital Sanctuary](https://www.digitalsanctuary.com/)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdevondragon%2Fspringuserframework","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdevondragon%2Fspringuserframework","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdevondragon%2Fspringuserframework/lists"}