π Certificate Generation System
A multi-tenant backend system for issuing, managing, and publicly verifying digital certificates β built with Spring Boot 3.4, Spring Security (JWT), PostgreSQL, and OpenPDF.
Organizations register, define courses with eligibility rules, enroll recipients, assign signatories, and generate branded PDF certificates β all through a secured REST API.
---
System Architecture
---
Request Lifecycle
Every API call flows through a consistent layered pipeline:
sequenceDiagram
participant C as Client
participant F as JwtAuthFilter
participant CT as Controller
participant S as Service
participant R as Repository
participant DB as PostgreSQL
participant EH as GlobalExceptionHandler
C->>F: HTTP Request + Bearer Token
F->>F: Extract & validate JWT
F->>F: Load OrganizationPrincipal
F->>CT: Authenticated request
CT->>S: Delegate business logic
S->>R: Data access
R->>DB: Query / Persist
DB-->>R: Result
R-->>S: Entity
S-->>CT: Response DTO
CT-->>C: HTTP 2xx + JSON
Note over S,EH: On failure
S--xEH: Throws domain exception
EH-->>C: Structured error response---
Domain Model
---
Security Architecture
Key security decisions:
| Aspect | Implementation | |---|---| | Identity source | JWT principal (no X-Org-Id header spoofing) | | Token type | Access + Refresh token pair | | Session control | currentAuthSessionId invalidates old tokens | | Password storage | BCrypt hashing | | Tenant isolation | All queries scoped by orgId from JWT |
---
Package Structure
src/main/java/com/niranjan/certificates/
βββ CertificatesGenerationApplication.java # Spring Boot entry point
βββ controller/
β βββ AuthController.java # Login, register, refresh
β βββ OrganizationController.java # Org profile & logo
β βββ CourseController.java # Course CRUD
β βββ RecipientController.java # Recipient CRUD
β βββ SignatoryController.java # Signatory CRUD + signature upload
β βββ CertificateController.java # Generate, list, download, verify
βββ dto/
β βββ request/ # Validated input payloads
β β βββ LoginRequest, RegisterRequest
β β βββ CourseRequest, RecipientRequest
β β βββ CertificateRequest, SignatoryRequest
β β βββ UpdateOrgRequest, ListQuery
β β βββ RefreshTokenRequest
β βββ response/ # Output payloads
β βββ AuthResponse, OrganizationResponse
β βββ CourseResponse, RecipientResponse
β βββ CertificateResponse, SignatoryResponse
β βββ VerifyResponse, PageResponse
β βββ ...
βββ entity/
β βββ Organization.java # Tenant root entity
β βββ Course.java # Course with minScore eligibility
β βββ Recipient.java # Enrolled learner
β βββ Signatory.java # Certificate signer
β βββ Certificate.java # Issued certificate
β βββ CertificateStatus.java # ISSUED / REVOKED enum
βββ exception/
β βββ GlobalExceptionHandler.java # Centralized error responses
β βββ ResourceNotFoundException.java
β βββ DuplicateResourceException.java
β βββ IneligibleRecipientException.java
βββ repository/
β βββ OrganizationRepository.java
β βββ CourseRepository.java
β βββ RecipientRepository.java
β βββ SignatoryRepository.java
β βββ CertificateRepository.java
βββ security/
β βββ SecurityConfig.java # Filter chain & endpoint rules
β βββ JwtAuthenticationFilter.java # Per-request token validation
β βββ JwtService.java # Token creation & parsing
β βββ AuthSessionService.java # Refresh token rotation
β βββ OrganizationPrincipal.java # Authenticated identity
β βββ OrganizationUserDetailsService.java # UserDetailsService impl
βββ service/
βββ CertificateService.java # Interface
βββ CourseService.java
βββ RecipientService.java
βββ SignatoryService.java
βββ OrganizationService.java
βββ PdfService.java
βββ ImageService.java
βββ FileStorageService.java
βββ impl/
βββ CertificateServiceImpl.java # Core cert logic + PDF orchestration
βββ CourseServiceImpl.java # Soft delete, minScore defaults
βββ RecipientServiceImpl.java # Post-issuance guards
βββ SignatoryServiceImpl.java # Default signatory management
βββ OrganizationServiceImpl.java # Profile & logo management
βββ PdfServiceImpl.java # OpenPDF rendering engine
βββ ImageServiceImpl.java # PDF-to-image conversion
βββ FileStorageServiceImpl.java # Local disk I/O---
Tech Stack
| Layer | Technology | |---|---| | Language | Java 21 | | Framework | Spring Boot 3.4.5 | | Web | Spring Web (REST) | | Security | Spring Security + JWT (jjwt 0.12.6) | | Persistence | Spring Data JPA / Hibernate | | Database | PostgreSQL | | Validation | Jakarta Bean Validation | | PDF Generation | OpenPDF 1.3.30 | | PDF Imaging | Apache PDFBox 3.0.3 | | Boilerplate | Lombok | | Build | Maven | | Testing | JUnit 5 + Mockito |
---
API Overview
Public Endpoints
| Method | Endpoint | Description | |---|---|---| | POST | /api/org/register | Register a new organization | | POST | /api/auth/login | Authenticate and receive tokens | | POST | /api/auth/refresh | Rotate refresh token | | GET | /api/verify/{uniqueCode} | Publicly verify a certificate |
Protected Endpoints (Bearer token required)
| Resource | Endpoints | Operations | |---|---|---| | Organization | /api/org/* | Get profile, update, upload logo | | Courses | /api/courses/* | CRUD + soft delete | | Recipients | /api/recipients/* | CRUD with post-issuance guards | | Signatories | /api/signatories/* | CRUD + set default + upload signature | | Certificates | /api/certificates/* | Generate, list, get, download PDF |
---
Business Rules
Certificate Issuance Guards
- Recipient and signatory must belong to the authenticated organization
- Recipient's course must be active
- Recipient's score must meet or exceed the course
minScore - Only one certificate per organizationβrecipient pair (enforced in code + DB constraint)
- Failed DB writes trigger best-effort PDF cleanup
Recipient Protection
- Course cannot be changed after a certificate has been issued
- Recipient cannot be deleted after a certificate has been issued
- Course must be active at the time of creation or update
Course Lifecycle
- Courses are soft-deleted (marked inactive) to preserve historical certificates
minScoredefaults to0when omitted; validated in range[0, 100]
---
Error Handling
All errors are normalized through GlobalExceptionHandler into a consistent response shape:
{
"status": 404,
"message": "Resource not found",
"timestamp": "2026-04-26T00:30:00"
}| Status | Meaning | |---|---| | 400 | Validation error or illegal operation | | 401 | Missing / expired / invalid token | | 403 | Not authorized to access resource | | 404 | Resource not found | | 409 | Duplicate certificate | | 422 | Recipient score below course minimum | | 500 | Unexpected server error |
---
Design Decisions
| Decision | Choice | Rationale | |---|---|---| | Multi-tenancy | Org ID from JWT, query-scoped | Prevents cross-tenant data leaks | | Primary keys | UUID | Non-sequential, safe for public URLs | | Service pattern | Interface + Impl | Thin controllers, testable business logic | | PDF engine | OpenPDF | Lightweight, no external service dependency | | File storage | Local disk /uploads | Simple for MVP, swappable to S3 later | | Course deletion | Soft delete | Preserves certificate history | | Certificate uniqueness | One per orgβrecipient | Prevents accidental duplicate issuance | | DI style | Constructor injection (Lombok) | Immutable, testable dependencies |