A full-stack cloud file storage system inspired by Google Drive, built with Spring Boot, JWT authentication, and AWS S3.
The platform supports multiple users with strict data isolation and secure file access through a browser-based UI.
- Backend: Spring Boot 3.5.10 (Java 17)
- Frontend: HTML5, CSS3, Vanilla JavaScript
- Authentication: JWT (Stateless)
- Security: Spring Security, BCrypt password hashing
- Database: PostgreSQL
- Storage: AWS S3 (Presigned URLs for secure uploads)
- Build Tool: Maven
- API Testing: Browser UI, Postman, or curl
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Frontend (HTML/CSS/JavaScript) β
β ββ Authentication Pages (Register/Login) β
β ββ File Management UI (Upload/List) β
ββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββ
β
β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Spring Boot REST API (Java 17) β
β ββ JWT Authentication Filter β
β ββ Auth Controller (Register/Login) β
β ββ User Controller (Profile) β
β ββ File Controller (Upload/List) β
ββββββββββββββββ¬ββββββββββββββββββββββββββββ¬βββββββββββββββ
β β
β β
ββββββββββββββββ ββββββββββββββββ
β PostgreSQL β β AWS S3 β
β Database β β Storage β
β (Metadata) β β (File Data) β
ββββββββββββββββ ββββββββββββββββ
JWT (JSON Web Tokens) provide stateless authentication. The token is generated upon login and stored on the client side.
Every subsequent request includes the JWT in the Authorization header, verified by the JwtAuthenticationFilter.
JWT is stateless, meaning no session data is stored on the serverβthe token contains all necessary information.
A user registers with an email and password via the frontend.
- Frontend sends POST request to
/auth/register - Backend validates email uniqueness
- Password is hashed using BCrypt with a random salt
- User record is stored in PostgreSQL
- Frontend redirects to login page
Details:
- Endpoint:
POST /auth/register - Request body:
{ "email": "user@example.com", "password": "password123" } - Password storage:
BCrypt automatically generates a unique salt and hashes the password.
Format:$2a$10$salt$hash
Even identical passwords produce different hashes.
A user authenticates with email and password.
- Frontend sends POST request to
/auth/login - Backend validates email exists
- BCrypt verifies password against stored hash
- JWT token is generated with user ID as payload
- Token is returned to frontend and stored in
localStorage - Frontend redirects to file management page
Details:
- Endpoint:
POST /auth/login - Request body:
{ "email": "user@example.com", "password": "password123" } - Response: JWT token (string)
- Token expiry: 900,000 ms (15 minutes)
- Token payload contains: User ID, issued time, expiration time
A JWT consists of three parts:
Header.Payload.Signature
-
Header: Algorithm and token type
{ "alg": "HS256", "typ": "JWT" } -
Payload: User claims (signed but not encrypted)
{ "userId": 123, "iat": 1706847000, "exp": 1706847900 } -
Signature: HMAC-SHA256 hash of header + payload with secret key
HMAC-SHA256(SECRET + HEADER + PAYLOAD)
The secret key (jwt.secret in application.yaml) is never sent to the client.
If any part of the token is modified, signature verification fails.
JwtAuthenticationFilter intercepts all incoming HTTP requests.
Flow:
- Extract JWT from
Authorization: Bearer <token>header - Validate token signature using secret key
- Validate token expiry
- Extract user ID from token payload
- Store authentication in
SecurityContext - Forward request to controller
If validation fails:
- Request is rejected immediately (401/403)
- Controllers are never reached
- Business logic never runs
- Data remains untouched
- Public:
/auth/register,/auth/login - Protected: All other endpoints require a valid JWT in
Authorizationheader
| Method | Endpoint | Description |
|---|---|---|
| POST | /auth/register |
Register new user with email & password |
| POST | /auth/login |
Authenticate and receive JWT token |
| Method | Endpoint | Description |
|---|---|---|
| GET | /me |
Get authenticated user ID (test endpoint) |
| Method | Endpoint | Description |
|---|---|---|
| POST | /files/upload-intent |
Create upload intent and get S3 presigned URL |
| POST | /files/{fileId}/complete |
Mark file upload as complete |
| GET | /files |
List all files owned by authenticated user |
The file upload uses a presigned URL approach for direct S3 uploads:
-
Create Upload Intent
- Frontend sends file metadata (name, size, content type)
- Backend generates unique S3 object key
- Backend stores file metadata in PostgreSQL with
PENDINGstatus - Backend generates presigned URL using AWS S3 API
- Returns
fileIdanduploadUrlto frontend
-
Upload to S3
- Frontend uploads file directly to S3 using presigned URL
- Backend is not involved in the actual file transfer
-
Complete Upload
- Frontend calls completion endpoint with
fileId - Backend verifies file exists in S3
- Backend updates file status to
UPLOADED
- Frontend calls completion endpoint with
Benefits:
- Backend doesn't handle large file payloads
- Direct S3 upload reduces latency
- Presigned URLs expire (security)
- User data isolated by
userIdprefix in S3 key
Multi-tenancy Implementation:
- Each file is tagged with
ownerUserId - File queries filter by authenticated user ID
- S3 keys use pattern:
user-{userId}/{uuid} - User can only access their own files
JWT Security:
- Extracted user ID from token is used for all data access
- No user ID parameter in API (extracted from JWT)
- Prevents ID tampering or unauthorized access
The frontend is split across multiple HTML pages:
- index.html - Landing page with register/login form
- login.html - Login-only page (if user not authenticated)
- drive.html - Main file management page (requires JWT)
- app.js - Shared JavaScript logic for all pages
- style.css, auth.css, drive.css - Page styling
Frontend Flow:
- User visits
index.htmlorlogin.html - Register or login
- JWT stored in
localStorage - Redirected to
drive.html - Logout clears token and redirects to login
- Java 17+
- Maven
- PostgreSQL running locally
- AWS S3 credentials (configured locally)
-
Configure Database (application.yaml)
spring: datasource: url: jdbc:postgresql://localhost:5432/mini_drive username: postgres password: 12345
-
Configure JWT Secret (application.yaml)
jwt: secret: 9f8e7d6c5b4a3a2f1e0d9c8b7a6f5e4d3c2b1a0f expiration: 900000 # 15 minutes
-
Configure AWS S3 Credentials
Use AWS CLI or environment variables:aws configure # or set AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION
mvn spring-boot:runBackend starts at: http://localhost:8080
Open in browser:
file:///path/to/mini-drive/frontend/index.html
Or serve with a simple HTTP server:
cd frontend
python -m http.server 8000
# Visit http://localhost:8000/index.htmlmini-drive/
βββ frontend/ # Browser UI
β βββ index.html # Landing page
β βββ login.html # Login page
β βββ drive.html # File management page
β βββ app.js # Shared JavaScript logic
β βββ style.css # Global styles
β βββ auth.css # Auth pages styling
β βββ drive.css # Drive page styling
β
βββ src/main/java/com/reranko/cloud_storage/mini_drive/
β βββ MiniDriveApplication.java # Spring Boot entry point
β βββ auth/ # Authentication module
β β βββ controller/
β β β βββ AuthController.java # Register/Login endpoints
β β β βββ UserController.java # User info endpoints
β β βββ service/
β β β βββ AuthService.java # Auth business logic
β β βββ jwt/
β β β βββ JwtService.java # JWT token generation/validation
β β βββ dto/
β β βββ RegisterRequest.java
β β βββ LoginRequest.java
β β
β βββ file/ # File management module
β β βββ controller/
β β β βββ FileController.java # Upload/List endpoints
β β βββ service/
β β β βββ S3UploadService.java # AWS S3 integration
β β βββ entity/
β β β βββ FileMetadata.java
β β β βββ FileStatus.java
β β βββ repository/
β β β βββ FileMetadataRepository.java
β β βββ dto/
β β βββ CreateFileRequest.java
β β βββ CreateFileResponse.java
β β βββ FileListItem.java
β β
β βββ user/ # User module
β β βββ entity/
β β β βββ User.java
β β βββ repository/
β β β βββ UserRepository.java
β β βββ service/
β β βββ UserService.java
β β
β βββ security/ # Security configuration
β β βββ JwtAuthenticationFilter.java
β β
β βββ config/ # Application configuration
β β βββ SecurityConfig.java
β β
β βββ exception/ # Custom exceptions
β β βββ ...
β β
β βββ storage/ # AWS S3 utilities
β β βββ ...
β β
β βββ common/ # Shared utilities
β βββ ...
β
βββ src/main/resources/
β βββ application.yaml # Configuration (DB, JWT, S3)
β βββ static/ # Static files (if needed)
β
βββ src/test/java/ # Integration tests
β βββ FileFlowIntegrationTest.java
β βββ MiniDriveApplicationTests.java
β
βββ pom.xml # Maven dependencies
βββ mvnw & mvnw.cmd # Maven wrapper
βββ README.md # This file
Run integration tests:
mvn testTest results are generated in target/surefire-reports/
From pom.xml:
spring-boot-starter-web- REST API supportspring-boot-starter-security- Authentication & authorizationspring-boot-starter-data-jpa- Database ORMspring-boot-starter-validation- Input validationspring-security-jwt- JWT support (if added)org.postgresql- PostgreSQL driversoftware.amazon.awssdk:s3- AWS S3 SDK (if added)