mirror of
https://github.com/s-frick/effigenix.git
synced 2026-03-28 17:19:56 +01:00
- Move Java backend to backend/ directory - Create frontend/ directory for TypeScript TUI and future WebUI - Update .gitignore for Node.js and worktrees - Update README.md with new repository structure - Copy documentation to backend/
677 lines
19 KiB
Markdown
677 lines
19 KiB
Markdown
# User Management - Dokumentation
|
|
|
|
**Bounded Context:** User Management (Generic Subdomain)
|
|
**Architektur:** DDD-Light + Clean Architecture
|
|
**Status:** MVP Implementation Complete
|
|
|
|
---
|
|
|
|
## Übersicht
|
|
|
|
User Management ist als **Generic Subdomain** klassifiziert - Commodity-Funktionalität ohne Wettbewerbsvorteil. Daher minimaler DDD-Aufwand:
|
|
- ✅ Einfache Entities (keine Aggregates)
|
|
- ✅ Transaction Script Pattern (keine komplexen Domain Services)
|
|
- ✅ Keine Domain Events
|
|
- ✅ Standard-Technologien (Spring Security, JWT, BCrypt)
|
|
|
|
### Warum wird User Management benötigt?
|
|
|
|
- **HACCP-Compliance:** Alle Qualitätsaktionen müssen einem Benutzer zugeordnet sein
|
|
- **GoBD-Compliance:** Audit Trail für alle geschäftskritischen Aktionen
|
|
- **Mehrfilialen-Support:** Benutzer müssen Filialen zugeordnet werden
|
|
- **Berechtigungskonzept:** Unterschiedliche Rollen für Produktion, Qualität, Verkauf
|
|
|
|
---
|
|
|
|
## Architektur
|
|
|
|
### Clean Architecture Layers
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────┐
|
|
│ REST API (Controllers) │
|
|
│ AuthController, UserController, RoleController │
|
|
└─────────────────────────────────────────────────────┘
|
|
↓
|
|
┌─────────────────────────────────────────────────────┐
|
|
│ Application Layer (Use Cases) │
|
|
│ CreateUser, AuthenticateUser, ChangePassword │
|
|
│ Transaction Script Pattern │
|
|
└─────────────────────────────────────────────────────┘
|
|
↓
|
|
┌─────────────────────────────────────────────────────┐
|
|
│ Domain Layer (DDD-Light) │
|
|
│ User, Role (Simple Entities) │
|
|
│ UserId, RoleId, PasswordHash (Value Objects) │
|
|
│ UserRepository, RoleRepository (Interfaces) │
|
|
└─────────────────────────────────────────────────────┘
|
|
↓
|
|
┌─────────────────────────────────────────────────────┐
|
|
│ Infrastructure Layer │
|
|
│ Spring Security, JWT, BCrypt, JPA, PostgreSQL │
|
|
└─────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
### Wichtige Design-Entscheidungen
|
|
|
|
| Entscheidung | Begründung |
|
|
|--------------|------------|
|
|
| **Eigene Implementation** statt Keycloak | Einfacher für MVP, volle Kontrolle, weniger Komplexität |
|
|
| **JWT** statt Sessions | Stateless, skalierbar, microservice-ready |
|
|
| **BCrypt Strength 12** | Sicher (~250ms), nicht zu langsam |
|
|
| **AuthorizationPort** (ACL) | Ermöglicht spätere Keycloak-Migration ohne BC-Änderungen |
|
|
| **Async Audit Logging** | Performance - blockiert Use Cases nicht |
|
|
| **No Domain Events** | Generic Subdomain - minimaler DDD-Aufwand |
|
|
|
|
---
|
|
|
|
## Domain Model
|
|
|
|
### Entities
|
|
|
|
#### User
|
|
```java
|
|
public class User {
|
|
private UserId id;
|
|
private String username;
|
|
private String email;
|
|
private PasswordHash passwordHash; // BCrypt
|
|
private Set<Role> roles;
|
|
private String branchId; // Filialzuordnung
|
|
private UserStatus status; // ACTIVE, INACTIVE, LOCKED
|
|
private LocalDateTime createdAt;
|
|
private LocalDateTime lastLogin;
|
|
}
|
|
```
|
|
|
|
#### Role (Reference Data)
|
|
```java
|
|
public class Role {
|
|
private RoleId id;
|
|
private RoleName name; // Enum: ADMIN, PRODUCTION_MANAGER, ...
|
|
private Set<Permission> permissions;
|
|
private String description;
|
|
}
|
|
```
|
|
|
|
### Value Objects
|
|
|
|
- `UserId` - UUID-basiert
|
|
- `RoleId` - UUID-basiert
|
|
- `PasswordHash` - BCrypt Hash (60 chars, $2a$12$...)
|
|
- `Permission` - Enum (RECIPE_READ, BATCH_WRITE, etc.)
|
|
- `RoleName` - Enum (ADMIN, PRODUCTION_MANAGER, etc.)
|
|
|
|
---
|
|
|
|
## Vordefinierte Rollen
|
|
|
|
| Rolle | Permissions | Zielgruppe |
|
|
|-------|-------------|------------|
|
|
| **ADMIN** | Alle (67) | Systemadministrator |
|
|
| **PRODUCTION_MANAGER** | RECIPE_*, BATCH_*, PRODUCTION_ORDER_*, STOCK_READ | Leiter Produktion |
|
|
| **PRODUCTION_WORKER** | RECIPE_READ, BATCH_*, LABEL_* | Produktionsmitarbeiter |
|
|
| **QUALITY_MANAGER** | HACCP_*, TEMPERATURE_LOG_*, CLEANING_RECORD_*, GOODS_INSPECTION_* | Qualitätsbeauftragter |
|
|
| **QUALITY_INSPECTOR** | TEMPERATURE_LOG_*, GOODS_INSPECTION_* | QM-Mitarbeiter |
|
|
| **PROCUREMENT_MANAGER** | PURCHASE_ORDER_*, GOODS_RECEIPT_*, SUPPLIER_* | Einkaufsleiter |
|
|
| **WAREHOUSE_WORKER** | STOCK_*, INVENTORY_COUNT_*, LABEL_* | Lagermitarbeiter |
|
|
| **SALES_MANAGER** | ORDER_*, INVOICE_*, CUSTOMER_* | Verkaufsleiter |
|
|
| **SALES_STAFF** | ORDER_READ/WRITE, CUSTOMER_READ, STOCK_READ | Verkaufsmitarbeiter |
|
|
|
|
Rollen werden bei DB-Initialisierung aus Seed-Daten geladen (Flyway Migration V002).
|
|
|
|
---
|
|
|
|
## Authorization Port (ACL für andere BCs)
|
|
|
|
### Warum AuthorizationPort?
|
|
|
|
**Problem:** Andere BCs brauchen Authentifizierung/Autorisierung, sollen aber nicht direkt von User Management abhängen.
|
|
|
|
**Lösung:** `AuthorizationPort` Interface im **Shared Kernel** - typsicher, action-oriented, ermöglicht Keycloak-Migration.
|
|
|
|
### Interface
|
|
|
|
```java
|
|
public interface AuthorizationPort {
|
|
boolean can(Action action);
|
|
void assertCan(Action action);
|
|
boolean can(Action action, ResourceId resource);
|
|
void assertCan(Action action, ResourceId resource);
|
|
ActorId currentActor();
|
|
Optional<BranchId> currentBranch();
|
|
}
|
|
```
|
|
|
|
### Typsichere Actions (Sealed Interface + Enums)
|
|
|
|
```java
|
|
// Shared Kernel - Basis-Interface
|
|
public interface Action {
|
|
// Marker interface
|
|
}
|
|
|
|
// Production BC - eigene Actions
|
|
public enum ProductionAction implements Action {
|
|
RECIPE_READ, RECIPE_WRITE, RECIPE_DELETE,
|
|
BATCH_READ, BATCH_WRITE, BATCH_COMPLETE,
|
|
PRODUCTION_ORDER_READ, PRODUCTION_ORDER_WRITE
|
|
}
|
|
|
|
// Quality BC
|
|
public enum QualityAction implements Action {
|
|
TEMPERATURE_LOG_READ, TEMPERATURE_LOG_WRITE,
|
|
CLEANING_RECORD_READ, CLEANING_RECORD_WRITE,
|
|
GOODS_INSPECTION_READ, GOODS_INSPECTION_WRITE
|
|
}
|
|
```
|
|
|
|
### Nutzung in anderen BCs
|
|
|
|
```java
|
|
// Production BC - Rezept erstellen
|
|
public class CreateRecipe {
|
|
private final AuthorizationPort authPort;
|
|
private final RecipeRepository recipeRepository;
|
|
private final AuditLogger auditLogger;
|
|
|
|
public Result<ApplicationError, RecipeDTO> execute(CreateRecipeCommand cmd) {
|
|
// ✅ Typsichere fachliche Authorization
|
|
authPort.assertCan(ProductionAction.RECIPE_WRITE);
|
|
|
|
// Business logic
|
|
Recipe recipe = Recipe.create(cmd.name(), cmd.ingredients());
|
|
recipeRepository.save(recipe);
|
|
|
|
// Audit logging mit ActorId
|
|
auditLogger.log(
|
|
AuditEvent.RECIPE_CREATED,
|
|
recipe.id(),
|
|
authPort.currentActor()
|
|
);
|
|
|
|
return Result.success(RecipeDTO.from(recipe));
|
|
}
|
|
}
|
|
```
|
|
|
|
### Action → Permission Mapping
|
|
|
|
```java
|
|
// Infrastructure Layer - ActionToPermissionMapper
|
|
@Component
|
|
public class ActionToPermissionMapper {
|
|
public Permission mapActionToPermission(Action action) {
|
|
// Type-safe pattern matching (Java 21)
|
|
if (action instanceof ProductionAction pa) {
|
|
return switch (pa) {
|
|
case RECIPE_READ -> Permission.RECIPE_READ;
|
|
case RECIPE_WRITE -> Permission.RECIPE_WRITE;
|
|
// ... exhaustive
|
|
};
|
|
}
|
|
// ... andere BCs
|
|
}
|
|
}
|
|
```
|
|
|
|
### Vorteile
|
|
|
|
1. ✅ **Typsicher** - Compiler prüft Actions, keine Tippfehler
|
|
2. ✅ **Fachlich** - Jeder BC spricht seine eigene Sprache
|
|
3. ✅ **Entkoppelt** - BCs kennen keine User/Roles/Permissions
|
|
4. ✅ **Flexibel** - Action-zu-Permission-Mapping änderbar ohne BC-Änderungen
|
|
5. ✅ **IDE-Support** - Auto-Completion für Actions
|
|
6. ✅ **Keycloak-ready** - Actions können auf Keycloak Policies gemappt werden
|
|
|
|
---
|
|
|
|
## REST API
|
|
|
|
### Authentication Endpoints (Public)
|
|
|
|
#### Login
|
|
```http
|
|
POST /api/auth/login
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"username": "admin",
|
|
"password": "admin123"
|
|
}
|
|
|
|
Response 200 OK:
|
|
{
|
|
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
|
"tokenType": "Bearer",
|
|
"expiresIn": 28800,
|
|
"expiresAt": "2026-02-18T02:00:00",
|
|
"refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
|
|
}
|
|
```
|
|
|
|
#### Logout
|
|
```http
|
|
POST /api/auth/logout
|
|
Authorization: Bearer {accessToken}
|
|
|
|
Response 204 No Content
|
|
```
|
|
|
|
#### Refresh Token
|
|
```http
|
|
POST /api/auth/refresh
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
|
|
}
|
|
|
|
Response 200 OK:
|
|
{
|
|
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
|
...
|
|
}
|
|
```
|
|
|
|
### User Management Endpoints (Authenticated)
|
|
|
|
#### Create User (ADMIN only)
|
|
```http
|
|
POST /api/users
|
|
Authorization: Bearer {accessToken}
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"username": "jdoe",
|
|
"email": "jdoe@example.com",
|
|
"password": "SecurePass123!",
|
|
"roleNames": ["PRODUCTION_WORKER"],
|
|
"branchId": "branch-123"
|
|
}
|
|
|
|
Response 201 Created
|
|
```
|
|
|
|
#### List Users
|
|
```http
|
|
GET /api/users
|
|
Authorization: Bearer {accessToken}
|
|
|
|
Response 200 OK:
|
|
[
|
|
{
|
|
"id": "user-123",
|
|
"username": "jdoe",
|
|
"email": "jdoe@example.com",
|
|
"roles": [...],
|
|
"branchId": "branch-123",
|
|
"status": "ACTIVE",
|
|
"createdAt": "2026-02-17T10:00:00",
|
|
"lastLogin": "2026-02-17T14:30:00"
|
|
}
|
|
]
|
|
```
|
|
|
|
#### Lock/Unlock User (ADMIN only)
|
|
```http
|
|
POST /api/users/{id}/lock
|
|
POST /api/users/{id}/unlock
|
|
Authorization: Bearer {accessToken}
|
|
|
|
Response 200 OK
|
|
```
|
|
|
|
#### Change Password
|
|
```http
|
|
PUT /api/users/{id}/password
|
|
Authorization: Bearer {accessToken}
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"currentPassword": "OldPass123!",
|
|
"newPassword": "NewPass456!"
|
|
}
|
|
|
|
Response 204 No Content
|
|
```
|
|
|
|
### Swagger UI
|
|
|
|
Interaktive API-Dokumentation: **http://localhost:8080/swagger-ui/index.html**
|
|
|
|
---
|
|
|
|
## Security
|
|
|
|
### JWT Token Structure
|
|
|
|
```json
|
|
{
|
|
"sub": "user-123",
|
|
"username": "jdoe",
|
|
"permissions": ["RECIPE_READ", "BATCH_WRITE", ...],
|
|
"branchId": "branch-123",
|
|
"iat": 1708185600,
|
|
"exp": 1708214400
|
|
}
|
|
```
|
|
|
|
- **Algorithm:** HS256 (HMAC-SHA256)
|
|
- **Secret:** Konfiguriert via `jwt.secret` in application.yml
|
|
- **Expiration:** 8 Stunden (Access Token), 7 Tage (Refresh Token)
|
|
|
|
### Password Security
|
|
|
|
- **Algorithm:** BCrypt
|
|
- **Strength:** 12 Rounds (~250ms hashing time)
|
|
- **Requirements:** Min. 8 Zeichen, 1 Großbuchstabe, 1 Kleinbuchstabe, 1 Zahl, 1 Sonderzeichen
|
|
|
|
### Spring Security Configuration
|
|
|
|
```yaml
|
|
# Stateless JWT Authentication
|
|
- sessionManagement: STATELESS
|
|
- CSRF: Disabled (safe for stateless JWT)
|
|
- Public Endpoints: /api/auth/login, /api/auth/refresh
|
|
- Protected Endpoints: /api/** (require authentication)
|
|
```
|
|
|
|
---
|
|
|
|
## Audit Logging (HACCP/GoBD)
|
|
|
|
### Compliance Requirements
|
|
|
|
- **HACCP:** Wer hat wann welche Temperatur gemessen?
|
|
- **GoBD:** Unveränderlicher Audit Trail für 10 Jahre
|
|
- **Retention:** 10 Jahre (gesetzlich), dann archivieren
|
|
|
|
### Audit Log Structure
|
|
|
|
```sql
|
|
CREATE TABLE audit_logs (
|
|
id VARCHAR(36) PRIMARY KEY,
|
|
event VARCHAR(100) NOT NULL, -- z.B. USER_CREATED, TEMPERATURE_RECORDED
|
|
entity_id VARCHAR(36), -- z.B. UserId, RecipeId, BatchId
|
|
performed_by VARCHAR(36), -- ActorId
|
|
details VARCHAR(2000), -- Zusätzliche Details
|
|
timestamp TIMESTAMP NOT NULL, -- Wann (Business-Zeit)
|
|
ip_address VARCHAR(45), -- Woher (Client IP)
|
|
user_agent VARCHAR(500), -- Womit (Browser/App)
|
|
created_at TIMESTAMP NOT NULL -- DB-Insert-Zeit
|
|
);
|
|
```
|
|
|
|
### Audit Events
|
|
|
|
- **User Management:** USER_CREATED, LOGIN_SUCCESS, PASSWORD_CHANGED
|
|
- **Quality BC:** TEMPERATURE_RECORDED, TEMPERATURE_CRITICAL, CLEANING_PERFORMED
|
|
- **Production BC:** BATCH_CREATED, BATCH_COMPLETED, RECIPE_MODIFIED
|
|
- **System:** SYSTEM_SETTINGS_CHANGED
|
|
|
|
### Performance
|
|
|
|
- **Async Logging:** `@Async` - blockiert Use Cases nicht
|
|
- **Separate Transaction:** `REQUIRES_NEW` - auch bei Business-TX-Rollback
|
|
- **Batch-Inserts:** Möglich für High-Volume-Szenarien
|
|
|
|
---
|
|
|
|
## Branch-Zuordnung (Mehrfilialen)
|
|
|
|
### User-Branch Beziehung
|
|
|
|
```java
|
|
public class User {
|
|
private String branchId; // Optional, null = ADMIN (global access)
|
|
}
|
|
```
|
|
|
|
### Data Filtering
|
|
|
|
**Strategie 1: Application Layer Filtering (MVP)**
|
|
|
|
```java
|
|
public class ListStock {
|
|
private final AuthorizationPort authPort;
|
|
|
|
public Result<ApplicationError, List<StockDTO>> execute() {
|
|
Optional<BranchId> actorBranch = authPort.currentBranch();
|
|
|
|
if (actorBranch.isPresent()) {
|
|
// Normaler User: Nur eigene Filiale
|
|
return Result.success(stockRepository.findByBranch(actorBranch.get()));
|
|
} else {
|
|
// ADMIN: Alle Filialen
|
|
return Result.success(stockRepository.findAll());
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
**Strategie 2: Database Row-Level Security (Post-MVP)**
|
|
|
|
```sql
|
|
-- PostgreSQL RLS (später)
|
|
CREATE POLICY user_branch_policy ON stock
|
|
USING (branch_id = current_setting('app.current_branch_id')::text);
|
|
```
|
|
|
|
---
|
|
|
|
## Deployment
|
|
|
|
### Umgebungsvariablen
|
|
|
|
```bash
|
|
# JWT Configuration
|
|
JWT_SECRET=YourVerySecretKeyMin256BitsForHS256Algorithm # Min. 256 Bits!
|
|
|
|
# Database
|
|
SPRING_DATASOURCE_URL=jdbc:postgresql://localhost:5432/effigenix
|
|
SPRING_DATASOURCE_USERNAME=effigenix
|
|
SPRING_DATASOURCE_PASSWORD=effigenix
|
|
|
|
# Flyway
|
|
SPRING_FLYWAY_ENABLED=true
|
|
```
|
|
|
|
### Docker Compose (PostgreSQL)
|
|
|
|
```yaml
|
|
version: '3.8'
|
|
services:
|
|
postgres:
|
|
image: postgres:15
|
|
environment:
|
|
POSTGRES_DB: effigenix
|
|
POSTGRES_USER: effigenix
|
|
POSTGRES_PASSWORD: effigenix
|
|
ports:
|
|
- "5432:5432"
|
|
volumes:
|
|
- postgres_data:/var/lib/postgresql/data
|
|
|
|
volumes:
|
|
postgres_data:
|
|
```
|
|
|
|
### Build & Run
|
|
|
|
```bash
|
|
# Build
|
|
mvn clean package
|
|
|
|
# Run
|
|
java -jar target/effigenix-erp-0.1.0-SNAPSHOT.jar
|
|
|
|
# Run mit Production Profile
|
|
java -jar target/effigenix-erp-0.1.0-SNAPSHOT.jar --spring.profiles.active=prod
|
|
```
|
|
|
|
---
|
|
|
|
## Entwickler-Guide
|
|
|
|
### Neue Rolle hinzufügen
|
|
|
|
1. **RoleName Enum erweitern** (`domain/usermanagement/RoleName.java`)
|
|
2. **Permissions zuweisen** in Flyway Seed-Daten (`V002__seed_roles_and_permissions.sql`)
|
|
3. **Migration ausführen** oder manuell in DB einfügen
|
|
|
|
### AuthorizationPort in neuem BC nutzen
|
|
|
|
1. **Action Enum erstellen** (z.B. `NewBCAction`)
|
|
```java
|
|
public enum NewBCAction implements Action {
|
|
ENTITY_READ, ENTITY_WRITE, ENTITY_DELETE
|
|
}
|
|
```
|
|
|
|
2. **Action in Shared Kernel registrieren** (nur für Dokumentation, nicht sealed)
|
|
|
|
3. **Mapping hinzufügen** in `ActionToPermissionMapper`
|
|
```java
|
|
if (action instanceof NewBCAction nba) {
|
|
return switch (nba) {
|
|
case ENTITY_READ -> Permission.ENTITY_READ;
|
|
case ENTITY_WRITE -> Permission.ENTITY_WRITE;
|
|
case ENTITY_DELETE -> Permission.ENTITY_DELETE;
|
|
};
|
|
}
|
|
```
|
|
|
|
4. **Permissions zu RoleName Enum hinzufügen**
|
|
```java
|
|
public enum Permission {
|
|
// ... existing
|
|
ENTITY_READ, ENTITY_WRITE, ENTITY_DELETE
|
|
}
|
|
```
|
|
|
|
5. **In Use Cases nutzen**
|
|
```java
|
|
authPort.assertCan(NewBCAction.ENTITY_WRITE);
|
|
```
|
|
|
|
### Custom Audit Event hinzufügen
|
|
|
|
1. **AuditEvent Enum erweitern**
|
|
```java
|
|
public enum AuditEvent {
|
|
// ... existing
|
|
MY_CUSTOM_EVENT
|
|
}
|
|
```
|
|
|
|
2. **In Use Case loggen**
|
|
```java
|
|
auditLogger.log(AuditEvent.MY_CUSTOM_EVENT, entityId, actorId);
|
|
```
|
|
|
|
---
|
|
|
|
## Testing
|
|
|
|
### Manuelles Testen (Swagger UI)
|
|
|
|
1. **Server starten:** `mvn spring-boot:run`
|
|
2. **Swagger öffnen:** http://localhost:8080/swagger-ui/index.html
|
|
3. **Login:** POST /api/auth/login mit `admin` / `admin123` (Seed-Daten erstellen)
|
|
4. **Token kopieren** aus Response
|
|
5. **Authorize** Button klicken, Token einfügen: `Bearer {token}`
|
|
6. **Endpoints testen**
|
|
|
|
### curl Beispiele
|
|
|
|
```bash
|
|
# Login
|
|
curl -X POST http://localhost:8080/api/auth/login \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"username":"admin","password":"admin123"}'
|
|
|
|
# Get Users (mit Token)
|
|
curl -X GET http://localhost:8080/api/users \
|
|
-H "Authorization: Bearer {accessToken}"
|
|
```
|
|
|
|
---
|
|
|
|
## Migration zu Keycloak (Zukunft)
|
|
|
|
### Warum ACL-Pattern vorbereitet ist
|
|
|
|
**Aktuell:** `SpringSecurityAuthorizationAdapter` → User Management BC
|
|
**Zukunft:** `KeycloakAuthorizationAdapter` → Keycloak
|
|
|
|
### Migrations-Schritte
|
|
|
|
1. **KeycloakAuthorizationAdapter implementieren**
|
|
- Implementiert `AuthorizationPort`
|
|
- Mappt Actions auf Keycloak Policies/Roles
|
|
|
|
2. **Spring Configuration umstellen**
|
|
- Keycloak Adapter in `SecurityConfig` einbinden
|
|
- JWT Validation auf Keycloak-Token umstellen
|
|
|
|
3. **Keine Änderungen in BCs nötig!**
|
|
- BCs nutzen weiterhin `authPort.assertCan(Action)`
|
|
- Mapping bleibt gleich
|
|
|
|
---
|
|
|
|
## Performance Considerations
|
|
|
|
| Komponente | Performance | Optimierung |
|
|
|------------|-------------|-------------|
|
|
| **Password Hashing** | ~250ms (BCrypt 12) | Akzeptabel für Login, async für bulk operations |
|
|
| **JWT Validation** | <1ms | Stateless, keine DB-Abfrage |
|
|
| **Audit Logging** | Async | Blockiert Use Cases nicht |
|
|
| **Authorization Check** | <5ms | In-Memory Permissions (aus JWT) |
|
|
|
|
### Produktions-Empfehlungen
|
|
|
|
1. **JWT Secret:** Min. 256 Bits, rotieren alle 90 Tage
|
|
2. **Refresh Token:** Redis-backed statt In-Memory
|
|
3. **Audit Logs:** Archivierung nach 10 Jahren (Legal-Hold)
|
|
4. **Rate Limiting:** Login-Endpunkt schützen (5 Versuche / 15 Min)
|
|
5. **HTTPS:** Nur HTTPS in Produktion (JWT im Header!)
|
|
|
|
---
|
|
|
|
## Troubleshooting
|
|
|
|
### "Invalid JWT signature"
|
|
- **Ursache:** JWT Secret geändert oder nicht konfiguriert
|
|
- **Lösung:** `JWT_SECRET` Umgebungsvariable prüfen
|
|
|
|
### "User not found" bei Login
|
|
- **Ursache:** Seed-Daten nicht geladen
|
|
- **Lösung:** Flyway Migrations ausführen (`mvn flyway:migrate`)
|
|
|
|
### "Access Denied" trotz richtiger Rolle
|
|
- **Ursache:** Permission fehlt in `ActionToPermissionMapper`
|
|
- **Lösung:** Mapping hinzufügen und neu deployen
|
|
|
|
### Audit Logs werden nicht geschrieben
|
|
- **Ursache:** Async nicht konfiguriert
|
|
- **Lösung:** `@EnableAsync` in Application Class prüfen
|
|
|
|
---
|
|
|
|
## Fazit
|
|
|
|
User Management als **Generic Subdomain** mit:
|
|
|
|
✅ **Minimaler DDD-Aufwand** - Einfache Entities, Transaction Scripts
|
|
✅ **Typsichere Authorization** - Action Enums statt String-Permissions
|
|
✅ **ACL-Pattern** - Ermöglicht Keycloak-Migration
|
|
✅ **HACCP/GoBD-Compliant** - Audit Logging mit 10-Jahres-Retention
|
|
✅ **Production-Ready** - JWT, BCrypt, Async, Multi-Branch
|
|
|
|
**Nächste Schritte:**
|
|
1. Seed-Daten für initialen Admin-User erstellen
|
|
2. Integration Tests schreiben
|
|
3. Production-Konfiguration (HTTPS, Rate Limiting, Redis)
|
|
4. Keycloak-Migration evaluieren (wenn OAuth2/SSO benötigt)
|