This project shows a minimal, production‑style way to secure a Spring Boot API using JWT (JSON Web Token) and Spring Security’s OAuth2 Resource Server support. This README explains the moving parts by naming the classes and describing their roles, without pasting the full source code.
- Login endpoint: POST /authenticate (public)
- Protected endpoint: GET /posts (requires a valid Bearer token)
- In‑memory demo user: user/password with ROLE_USER
- HS256 (HMAC‑SHA256) symmetric signing for JWTs, using a base64‑encoded secret
Server default port: 8080
spring-boot-starter-web: MVC/JSON to expose REST endpoints.spring-boot-starter-security: core security filters and authentication/authorization infrastructure.spring-boot-starter-oauth2-resource-server: JWT validation machinery, so the app can accept and validate Bearer tokens.
Why “Resource Server”? Because it gives you robust JWT validation (signature + claims) with minimal setup and first‑class Spring Security integration.
- A JWT is a compact string like header.payload.signature.
- Client authenticates once, server issues a signed token; the client then sends it in Authorization: Bearer on subsequent requests.
- The server validates the signature and checks claims like expiration for every request.
- This demo uses HS256 (symmetric): one shared secret is used to sign and verify. Keep it safe and base64‑encoded.
-
SecurityConfiguration- Enables CORS defaults, disables CSRF (stateless API), permits POST /authenticate, and requires authentication for other endpoints.
- Activates OAuth2 Resource Server with JWT so incoming Bearer tokens are decoded and validated.
-
JwtConfiguration- Reads base64 secret from
app.jwt.private-key(application.properties). - Exposes JwtEncoder (to issue tokens) and JwtDecoder (to validate tokens) using Nimbus.
- Uses
HS256(MacAlgorithm.HS256) for signing and verification.
- Reads base64 secret from
-
DomainUserDetailService- Provides a single in‑memory user for the demo: username: user, password: password, role: ROLE_USER.
- Note: {noop} is used for the demo; use a PasswordEncoder (e.g., BCrypt) in real apps.
-
JwtTokenService- Authenticates username/password using Spring’s AuthenticationManager.
- If successful, builds a JWT with claims:
sub: usernameiat/exp: issued‑at and expiration (30 minutes)auth: space‑separated authorities (e.g., ROLE_USER)
- Signs the token with
HS256via JwtEncoder and returns it.
-
AuthenticationController- Exposes
POST /authenticate(consumesapplication/json with {"email","password"}). - On success, returns the token as
{ "id_token": "<jwt>" }and also sets the Authorization response header toBearer <jwt>.
- Exposes
-
PostController- Exposes
GET /posts. Due to the security rules, this endpoint requires a valid Bearer token and returns sample data.
- Exposes
-
LoginRequest- Simple request body contract for
/authenticate: email and password.
- Simple request body contract for
-
JwtToken- Simple response body contract for
/authenticate: id_token field containing the JWT.
- Simple response body contract for
in application.properties file:
spring.application.name=jwt-autentication-demo
app.jwt.private-key=<your‑base64‑secret>Important : app.jwt.private-key must be a base64‑encoded secret. Generate one, for example with:
- OpenSSL (macOS/Linux):
openssl rand -base64 32 - Java:
Base64.getEncoder().encodeToString(SecureRandom.generateSeed(32))
Do not commit real secrets to version control. Use environment variables or your secrets manager in real deployments.
- Set a proper base64 secret in
src/main/resources/application.properties:app.jwt.private-key=<paste-your-base64-secret> - Build and run:
./mvnw spring-boot:run
- Server listens on http://localhost:8080
- Get a token:
curl -i -X POST http://localhost:8080/authenticate \
-H 'Content-Type: application/json' \
-d '{"email":"user", "password":"password"}'Copy id_token from the JSON response body (it also appears in the Authorization header).
- Call a protected endpoint:
TOKEN="<paste-id_token>"
curl -i http://localhost:8080/posts \
-H "Authorization: Bearer $TOKEN"You should receive 200 OK with a JSON array containing one sample post.
- 401 Unauthorized on protected endpoints: ensure you send Authorization: Bearer and the token is not expired.
- 401 on /authenticate: verify SecurityConfiguration permits POST /authenticate (it does in this project).
- Secrets: use different secrets per environment; rotate regularly.
- Passwords: never use {noop} in production; enable a strong PasswordEncoder and store hashed passwords.
- Client submits credentials to /authenticate.
- JwtTokenService authenticates using the configured UserDetailsService, builds claims, and signs a JWT.
- Client calls protected endpoints with Authorization: Bearer .
- Resource server filters validate the token and populate the SecurityContext; the controller executes if valid.
- Switch to RSA (RS256) with private/public keys for signing/verification.
- Back your users with a database and add a proper PasswordEncoder.
- Add fine‑grained authorization (e.g., role‑based rules per endpoint).
- Introduce refresh tokens or token revocation depending on your requirements.