Skip to content

kanojiaatharva/SpringSecurity

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

7 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

SpringSecurity — Stateless JWT Authentication with Spring Boot

A focused, production-oriented reference implementation of stateless JWT-based authentication and authorization built on Spring Boot 3 and Spring Security 6. The project demonstrates the complete security pipeline — from user registration and BCrypt-hashed credential storage through JWT issuance, per-request token validation, and protected resource access — without relying on HTTP sessions.


Table of Contents


Overview

Business Purpose
This application provides a self-contained, stateless authentication service that any front-end or service consumer can integrate with. Clients register an account, authenticate to receive a signed JWT, and then present that token on every subsequent request to access protected resources.

Problem Being Solved
Traditional session-based authentication requires server-side session storage, which complicates horizontal scaling and increases infrastructure coupling. This project eliminates session state entirely by encoding all identity information inside a cryptographically signed JWT, allowing any node in a cluster to authenticate a request independently.

Key Capabilities

Capability Detail
User self-registration Password hashing via BCrypt (strength factor 12) before persistence
Stateless JWT login HMAC-SHA-256 signed tokens issued on successful credential verification
Per-request JWT validation Custom OncePerRequestFilter intercepts and validates every inbound token
Protected resource access All endpoints except /register and /login require a valid Bearer token
In-memory student resource A protected /students resource illustrates authorized data retrieval

Architecture

High-Level Architecture

┌─────────────┐     HTTP      ┌──────────────────────────────────────────────┐
│             │  ──────────►  │              Spring Boot Application         │
│   Client    │               │                                              │
│ (Browser /  │  ◄──────────  │  ┌──────────┐  ┌──────────┐  ┌────────────┐  │
│  API Tool)  │    JSON/JWT   │  │Controller│  │ Service  │  │ Repository │  │
└─────────────┘               │  └──────────┘  └──────────┘  └────────────┘  │
                              │                                     │        │
                              │                              ┌──────▼──────┐ │
                              │                              │  PostgreSQL │ │
                              │                              └─────────────┘ │
                              └──────────────────────────────────────────────┘

Layered Architecture

┌──────────────────────────────────────────────────────┐
│                  Presentation Layer                  │
│          StudentController  ·  UserController        │
├──────────────────────────────────────────────────────┤
│                  Security Layer                      │
│       JwtFilter  ·  SecurityConfig  ·  UserPrincipal │
├──────────────────────────────────────────────────────┤
│                  Service Layer                       │
│  UserService  ·  JWTService  ·  MyUserDetailsService │
├──────────────────────────────────────────────────────┤
│                  Repository Layer                    │
│                      UserRepo                        │
├──────────────────────────────────────────────────────┤
│                  Domain Layer                        │
│             Users  ·  Student  ·  UserPrincipal      │
├──────────────────────────────────────────────────────┤
│                Infrastructure Layer                  │
│          PostgreSQL  ·  Spring Data JPA              │
└──────────────────────────────────────────────────────┘

Request Lifecycle

sequenceDiagram
    participant C as Client
    participant JF as JwtFilter
    participant SC as SecurityContext
    participant Ctrl as Controller
    participant Svc as Service

    C->>JF: HTTP Request (Authorization: Bearer <token>)
    JF->>JF: Extract token from Authorization header
    JF->>JF: extractUserName(token)
    JF->>Svc: loadUserByUsername(username)
    Svc-->>JF: UserDetails
    JF->>JF: validateToken(token, userDetails)
    alt Token valid
        JF->>SC: Set UsernamePasswordAuthenticationToken
        JF->>Ctrl: Forward request
        Ctrl-->>C: 200 OK + Response Body
    else Token invalid / missing
        JF->>Ctrl: Forward request (no SecurityContext set)
        Ctrl-->>C: 401 Unauthorized (Spring Security)
    end
Loading

Authentication Flow

sequenceDiagram
    participant C as Client
    participant UC as UserController
    participant US as UserService
    participant AM as AuthenticationManager
    participant MUDS as MyUserDetailsService
    participant JWTs as JWTService
    participant DB as PostgreSQL

    C->>UC: POST /login {username, password}
    UC->>US: verify(user)
    US->>AM: authenticate(UsernamePasswordAuthenticationToken)
    AM->>MUDS: loadUserByUsername(username)
    MUDS->>DB: findByUsername(username)
    DB-->>MUDS: Users entity
    MUDS-->>AM: UserPrincipal (UserDetails)
    AM->>AM: BCrypt.matches(rawPassword, hashedPassword)
    alt Credentials valid
        AM-->>US: Authentication (isAuthenticated = true)
        US->>JWTs: generateToken(username)
        JWTs-->>US: Signed JWT string
        US-->>UC: JWT string
        UC-->>C: 200 OK — JWT token
    else Credentials invalid
        AM-->>US: AuthenticationException
        US-->>UC: "fail"
        UC-->>C: "fail"
    end
Loading

Registration Flow

sequenceDiagram
    participant C as Client
    participant UC as UserController
    participant US as UserService
    participant DB as PostgreSQL

    C->>UC: POST /register {id, username, password}
    UC->>US: register(user)
    US->>US: BCryptPasswordEncoder(strength=12).encode(password)
    US->>DB: save(user with hashed password)
    DB-->>US: Persisted Users entity
    US-->>UC: Users entity
    UC-->>C: 200 OK — Users entity (with hashed password)
Loading

Technology Stack

Component Technology Version
Language Java 17
Framework Spring Boot 3.5.3
Security Spring Security 6.x (managed by Boot)
Persistence Spring Data JPA 3.x (managed by Boot)
ORM Hibernate 6.x (managed by Boot)
Database PostgreSQL Compatible with 42.7.7 driver
JDBC Driver PostgreSQL JDBC Driver 42.7.7
JWT Library JJWT (jjwt-api / jjwt-impl / jjwt-jackson) 0.12.6
Build Tool Apache Maven (via Maven Wrapper) 3.x
Testing JUnit 5 + Spring Boot Test + Spring Security Test Managed by Boot
Servlet API Jakarta Servlet API Provided by container
Java Crypto javax.crypto.KeyGenerator (HmacSHA256) JDK built-in

Features

  • User Registration — Accepts {id, username, password}, hashes the password with BCrypt (cost factor 12), and persists the user to PostgreSQL.
  • JWT-Based Login — Authenticates credentials via Spring Security's AuthenticationManager and returns a signed JWT on success.
  • Stateless Session Management — Sessions are never created (SessionCreationPolicy.STATELESS); every request is independently authenticated from its token.
  • Custom JWT FilterJwtFilter (extends OncePerRequestFilter) inspects the Authorization header on every request, validates the token, and populates the SecurityContext.
  • BCrypt Password Hashing — Registration encodes passwords with BCrypt at strength 12 before storage; no plaintext passwords are ever persisted.
  • DAO Authentication ProviderDaoAuthenticationProvider delegates credential verification to MyUserDetailsService + BCrypt comparator.
  • Protected Student Resource — A /students endpoint (GET and POST) that is accessible only to authenticated callers, demonstrating resource protection.
  • CSRF Inspection Endpoint — A /csrf-token utility endpoint (CSRF is disabled globally for stateless API use).
  • Per-Request HMAC-SHA256 Token Signing — JWTs are signed with a dynamically generated HmacSHA256 secret key created at application startup.
  • HTTP Basic fallback — HTTP Basic authentication is enabled as a secondary mechanism alongside JWT Bearer token support.

Project Structure

d:\WorkSpace\Java_WorkSpace\SpringSecurity
├── pom.xml                                          # Maven build descriptor
├── mvnw / mvnw.cmd                                  # Maven Wrapper scripts
├── src
│   ├── main
│   │   ├── java
│   │   │   └── com.ak.SpringSecutity
│   │   │       ├── SpringSecutityApplication.java   # Bootstrap entry point
│   │   │       ├── config/
│   │   │       │   ├── SecurityConfig.java          # Spring Security configuration
│   │   │       │   └── JwtFilter.java               # JWT request interception filter
│   │   │       ├── controller/
│   │   │       │   ├── UserController.java          # /register and /login endpoints
│   │   │       │   └── StudentController.java       # Protected /students resource
│   │   │       ├── model/
│   │   │       │   ├── Users.java                   # JPA entity for persisted users
│   │   │       │   ├── UserPrincipal.java           # Spring Security UserDetails adapter
│   │   │       │   └── Student.java                 # In-memory DTO for student data
│   │   │       ├── repo/
│   │   │       │   └── UserRepo.java                # Spring Data JPA repository
│   │   │       └── service/
│   │   │           ├── UserService.java             # Registration and login orchestration
│   │   │           ├── JWTService.java              # Token generation and validation
│   │   │           └── MyUserDetailsService.java    # UserDetailsService implementation
│   │   └── resources
│   │       └── application.properties               # Application and datasource config
│   └── test
│       └── java
│           └── com.ak.SpringSecutity
│               └── SpringSecutityApplicationTests.java  # Context load smoke test
└── target/                                          # Compiled output (gitignored)

Package Responsibilities

Package Responsibility
config Defines the SecurityFilterChain, authentication provider wiring, AuthenticationManager bean, and the JwtFilter that intercepts every HTTP request.
controller Thin HTTP layer. UserController delegates registration and login to UserService. StudentController exposes the protected student resource.
model Domain objects. Users is the JPA-managed entity. UserPrincipal is the Spring Security bridge (UserDetails). Student is an in-memory data class.
repo Spring Data JPA repository for Users. Provides CRUD plus a derived findByUsername query.
service Business logic. UserService orchestrates registration (hashing) and login (authentication + JWT issuance). JWTService encapsulates all JJWT operations. MyUserDetailsService is the UserDetailsService contract implementation.

Security Implementation

Authentication Mechanism

Authentication is performed via Spring Security's DaoAuthenticationProvider, which delegates to:

  1. MyUserDetailsService.loadUserByUsername() — retrieves the Users entity from PostgreSQL and wraps it in a UserPrincipal.
  2. BCryptPasswordEncoder(12) — compares the submitted raw password with the stored BCrypt hash.

Authorization Mechanism

Authorization is route-based. The SecurityFilterChain applies the following rules:

/register   →  permitAll()   (public)
/login      →  permitAll()   (public)
/**         →  authenticated() (requires valid JWT or HTTP Basic credentials)

JWT Flow

┌────────────────────────────────────────────────────────────────────┐
│ JWTService                                                          │
│                                                                     │
│  Constructor:                                                       │
│    KeyGenerator("HmacSHA256") → SecretKey → Base64-encode → store  │
│                                                                     │
│  generateToken(username):                                           │
│    Jwts.builder()                                                   │
│      .subject(username)                                             │
│      .issuedAt(now)                                                 │
│      .expiration(now + 300 minutes)                                 │
│      .signWith(HmacSHA256 key)                                      │
│      .compact()  →  JWT string                                      │
│                                                                     │
│  validateToken(token, userDetails):                                 │
│    extractUserName(token) == userDetails.getUsername()              │
│    && !isTokenExpired(token)                                        │
└────────────────────────────────────────────────────────────────────┘

Token lifetime: 300 minutes (18,000 seconds) from issuance.
Signing algorithm: HMAC-SHA-256.
Key lifecycle: Generated fresh each time the application starts (in-memory; not persisted). All previously issued tokens become invalid on restart.

Password Encryption

Property Value
Algorithm BCrypt
Cost factor (work factor) 12
Implementation org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
When applied UserService.register() before repo.save()
Verification DaoAuthenticationProvider via PasswordEncoder.matches()

Security Filters

The filter chain executes in this order on every request:

Incoming Request
      │
      ▼
 JwtFilter (OncePerRequestFilter)
      │  ── extracts Bearer token from Authorization header
      │  ── validates token and sets SecurityContext
      │
      ▼
 UsernamePasswordAuthenticationFilter (Spring built-in)
      │
      ▼
 ... (remaining Spring Security default filters)
      │
      ▼
 DispatcherServlet → Controller

JwtFilter is registered before UsernamePasswordAuthenticationFilter via http.addFilterBefore(...).

Security Configuration

// CSRF disabled — stateless API, no cookie-based sessions
http.csrf(customizer -> customizer.disable());

// Route-level authorization
http.authorizeHttpRequests(request ->
    request.requestMatchers("register", "login").permitAll()
           .anyRequest().authenticated());

// HTTP Basic enabled as secondary authentication mechanism
http.httpBasic(Customizer.withDefaults());

// No sessions — each request carries its own credentials
http.sessionManagement(session ->
    session.sessionCreationPolicy(SessionCreationPolicy.STATELESS));

// JWT filter wired in before the username/password filter
http.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);

Role Hierarchy

Every authenticated user is assigned a single authority: USER.

// UserPrincipal.java
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
    return Collections.singleton(new SimpleGrantedAuthority("USER"));
}

No role-based differentiation is implemented at this stage; all authenticated principals are treated equally.


API Documentation

Method Endpoint Description Authentication Required Request Body Response
POST /register Register a new user account. Password is BCrypt-hashed before persistence. ❌ Public {"id": 1, "username": "alice", "password": "secret"} Users entity (with hashed password)
POST /login Authenticate with credentials. Returns a signed JWT on success. ❌ Public {"username": "alice", "password": "secret"} JWT string
GET /students Retrieve the list of all students. ✅ Bearer JWT [{"id":1,"name":"Atharva","marks":100}, ...]
POST /students Add a new student to the in-memory list. ✅ Bearer JWT {"id": 3, "name": "Ravi", "marks": 85} Student object
GET /csrf-token Retrieve the current CSRF token (utility endpoint; CSRF is disabled globally). ✅ Bearer JWT CsrfToken object

Request / Response Examples

Registration

POST /register
Content-Type: application/json

{
  "id": 1,
  "username": "alice",
  "password": "mysecretpassword"
}

Login

POST /login
Content-Type: application/json

{
  "username": "alice",
  "password": "mysecretpassword"
}

# Response: eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhbGljZSIsImlhdCI6...

Authenticated Request

GET /students
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhbGljZSIs...

Database Design

Entities

Users (JPA Entity → table users)

Column Java Type JPA Annotation Constraint Description
id int @Id Primary Key, manually assigned Unique user identifier
username String None declared (effectively unique by lookup) Login username
password String Not null (enforced by BCrypt step) BCrypt-hashed password

Note: @GeneratedValue is not used; the caller is responsible for supplying a unique id on registration.

Student (Not a JPA entity — in-memory only)

Field Java Type Description
id int Student identifier
name String Student name
marks int Student score

Student is managed entirely in a List inside StudentController and is not persisted to the database.

Relationships

No inter-entity relationships exist at this stage. The Users entity is standalone.

Schema

Spring Data JPA with Hibernate will auto-derive the users table DDL at startup based on @Entity and @Id annotations. The effective schema is:

CREATE TABLE users (
    id       INTEGER     NOT NULL,
    username VARCHAR(255),
    password VARCHAR(255),
    PRIMARY KEY (id)
);

Configuration

application.properties

# Application name
spring.application.name=SpringSecutity

# Default in-memory Spring Security user (used for HTTP Basic before database auth)
spring.security.user.name=atharva
spring.security.user.password=admin

# PostgreSQL datasource
spring.datasource.url=jdbc:postgresql://localhost:5432/SpringSecurityDB
spring.datasource.username=Atharva
spring.datasource.password=0000

Required Configuration Properties

Property Description Sensitivity
spring.datasource.url JDBC URL for your PostgreSQL instance Medium
spring.datasource.username Database login username Medium
spring.datasource.password Database login password High — externalize in production
spring.security.user.name Default HTTP Basic username (development only) Low
spring.security.user.password Default HTTP Basic password (development only) High — remove in production

JPA / DDL Configuration

No spring.jpa.* or spring.jpa.hibernate.ddl-auto property is set explicitly. Hibernate's default behavior applies (none or validate, depending on the dialect's auto-detection). To auto-create the schema on first run, add:

spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true

JWT Secret Key

The HMAC-SHA256 secret key is generated at runtime inside the JWTService constructor using javax.crypto.KeyGenerator. It is not configurable through application.properties. This means:

  • All tokens issued before an application restart become invalid after the restart.
  • In a multi-instance deployment, different instances will generate different keys, making tokens non-portable across nodes.

For production, the secret key must be externalised (see Future Improvements).


Getting Started

Prerequisites

Requirement Version
JDK 17 or later
Apache Maven 3.6+ (or use the included mvnw wrapper)
PostgreSQL 13 or later

Installation

# Clone the repository
git clone https://github.com/<your-org>/SpringSecurity.git
cd SpringSecurity

Configuration

  1. Create the PostgreSQL database:
CREATE DATABASE "SpringSecurityDB";
CREATE USER "Atharva" WITH PASSWORD '0000';
GRANT ALL PRIVILEGES ON DATABASE "SpringSecurityDB" TO "Atharva";
  1. Update src/main/resources/application.properties with your PostgreSQL credentials:
spring.datasource.url=jdbc:postgresql://localhost:5432/SpringSecurityDB
spring.datasource.username=<your-db-username>
spring.datasource.password=<your-db-password>

# Enable auto schema creation on first run
spring.jpa.hibernate.ddl-auto=update

Running Locally

# Using the Maven Wrapper (recommended)
./mvnw spring-boot:run

# On Windows
mvnw.cmd spring-boot:run

# Or with a locally installed Maven
mvn spring-boot:run

The application starts on http://localhost:8080 by default.

Running Tests

# Run all tests
./mvnw test

# On Windows
mvnw.cmd test

Build & Deployment

Maven Commands

# Compile the source
./mvnw compile

# Run tests
./mvnw test

# Package into an executable JAR (skipping tests)
./mvnw package -DskipTests

# Clean previous builds and repackage
./mvnw clean package

The executable JAR is produced at:

target/SpringSecutity-0.0.1-SNAPSHOT.jar

Running the Packaged JAR

java -jar target/SpringSecutity-0.0.1-SNAPSHOT.jar

Production Deployment Considerations

Concern Recommendation
Secret key management Inject the JWT signing key via environment variable or a secrets manager (Vault, AWS Secrets Manager).
Database credentials Use environment variables or a secrets store; never commit credentials to source control.
DDL management Disable ddl-auto in production; use Flyway or Liquibase for controlled schema migrations.
HTTPS Place the application behind a TLS-terminating reverse proxy (nginx, AWS ALB).
Logging Configure structured JSON logging for log aggregation pipelines.
Health checks Add Spring Boot Actuator (/actuator/health) for load-balancer and Kubernetes probes.
Token invalidation Implement a token revocation mechanism (e.g., deny-list in Redis) before deployment.
Multiple instances Share the JWT signing key across all instances via external configuration.

Error Handling

There is no custom @ControllerAdvice or @ExceptionHandler implemented in this version. Error handling is delegated to Spring Security's default mechanisms:

Scenario Behavior
Missing or malformed Authorization header Request passes through JwtFilter without setting a SecurityContext. Spring Security returns 401 Unauthorized.
Expired JWT JwtFilter.validateToken() returns false; SecurityContext is not populated; Spring Security returns 401 Unauthorized.
Invalid token signature Jwts.parser().parseSignedClaims() throws a JwtException; the exception propagates and results in a 500 Internal Server Error (unhandled in current version).
User not found during loadUserByUsername MyUserDetailsService throws UsernameNotFoundException, which Spring Security translates to 401 Unauthorized.
Registration with duplicate id Hibernate/PostgreSQL throws a DataIntegrityViolationException, resulting in a 500 Internal Server Error (unhandled in current version).

Testing

Current Test Coverage

Test Class Type What it Verifies
SpringSecutityApplicationTests Integration (Spring Boot context load) The complete Spring application context assembles without errors — all beans are wired correctly, the database connection is available, and auto-configuration succeeds.

The contextLoads test is a smoke test that validates the entire dependency graph (security configuration, JWT filter, authentication provider, repository, and service beans) at startup time.

Running Tests

./mvnw test

Test Dependencies

Dependency Purpose
spring-boot-starter-test JUnit 5, Mockito, AssertJ, Spring Test utilities
spring-security-test Security-aware mock MVC support, @WithMockUser, @WithSecurityContext

Engineering Highlights

Clean Architecture Practices

  • Separation of concerns — Security configuration, business logic, and data access are in clearly delineated packages. No cross-layer coupling.
  • Adapter patternUserPrincipal acts as an adapter between the domain Users entity and Spring Security's UserDetails contract, keeping the domain model free of security framework dependencies.
  • Single ResponsibilityJWTService owns only token operations; UserService orchestrates only registration and login; MyUserDetailsService handles only user lookup.

Security Best Practices

  • Stateless designSessionCreationPolicy.STATELESS ensures no server-side session is ever created, which is appropriate for API-first architectures.
  • BCrypt with high cost factor — Using strength 12 (vs. the common default of 10) meaningfully increases resistance to offline brute-force attacks.
  • OncePerRequestFilter — Guarantees JWT validation runs exactly once per request, regardless of how many filter chains are involved.
  • Bearer token isolation — The JwtFilter only sets the SecurityContext when the token is valid; malformed or expired tokens result in a clean unauthenticated state.
  • CSRF disabled appropriately — CSRF protection is correctly disabled for a stateless REST API that does not rely on browser cookie-based sessions.

Notable Engineering Decisions

  • ApplicationContext lookup in JwtFilterJwtFilter fetches MyUserDetailsService from the ApplicationContext directly rather than injecting it as a field. This is an intentional design choice to avoid a circular dependency that would arise from Spring Security's bean initialization order when UserDetailsService and the SecurityConfig are wired together.
  • Dynamic key generation — Generating the HMAC key at startup rather than hardcoding it prevents accidental secret exposure in source control, though it introduces the restart-invalidation trade-off documented above.

Future Improvements

The following enterprise-grade enhancements are recommended for production readiness:

Area Improvement
JWT Key Management Externalise the HMAC secret to application.properties (injected via environment variable or Vault) so the key is stable across restarts and consistent across cluster nodes.
Token Expiry Make token TTL configurable via application.properties (app.jwt.expiration-ms).
Refresh Token Implement a refresh token endpoint (/refresh) to issue new access tokens without re-authentication, reducing credential exposure.
Token Revocation Implement a deny-list (backed by Redis) to invalidate tokens before expiry (e.g., on logout or password change).
Role-Based Access Control Persist user roles/authorities in the database and enforce method-level security with @PreAuthorize annotations.
Database ID Generation Replace the manually assigned int id in Users with @GeneratedValue(strategy = GenerationType.IDENTITY) to prevent duplicate-key errors.
Schema Migration Integrate Flyway or Liquibase for version-controlled, repeatable database schema evolution.
Global Exception Handling Add a @ControllerAdvice with @ExceptionHandler methods for JwtException, DataIntegrityViolationException, UsernameNotFoundException, and BadCredentialsException to return consistent, structured error responses.
DTO Layer Introduce request/response DTOs (e.g., RegisterRequest, LoginResponse) to decouple the API contract from the JPA entity and avoid serialising the hashed password in the registration response.
Input Validation Add Jakarta Bean Validation (@NotBlank, @Size) to request bodies and enable @Valid in controller method signatures.
Actuator & Observability Add spring-boot-starter-actuator for health, metrics, and info endpoints; integrate with Prometheus/Grafana for operational visibility.
Containerisation Add a Dockerfile and docker-compose.yml (with a PostgreSQL service) to enable one-command local setup and reproducible deployments.
CI/CD Pipeline Add a GitHub Actions workflow for automated build, test, and Docker image publication on every push.
API Documentation Integrate SpringDoc OpenAPI (springdoc-openapi-starter-webmvc-ui) to auto-generate and serve Swagger UI at /swagger-ui.html.
Test Coverage Write unit tests for JWTService, UserService, and MyUserDetailsService using Mockito, and integration tests for the auth endpoints using MockMvc with @WithMockUser.

About

A focused, production-oriented reference implementation of stateless JWT-based authentication and authorization built on Spring Boot 3 and Spring Security 6. The project demonstrates the complete security pipeline — from user registration and BCrypt-hashed credential storage through JWT issuance, per-request token validation, and protected resource

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages