Generating & Validating JWTs
Generating & Validating JWTs
The previous lesson explained what a JWT is. This lesson shows you how to build one and — equally important — how to verify one safely in a Spring Boot 3 application. By the end you will have a self-contained JwtService that your authentication filter and your tests can both depend on.
Choosing a Library: JJWT
The Java JWT (JJWT) library by Stormpath/Okta is the most widely used JWT library in the Spring ecosystem. It provides a fluent builder API for both generation and parsing, and it handles the Base64url encoding, signature computation, and claim validation for you. Add the three required JARs to your pom.xml:
The split into -api, -impl, and -jackson is intentional: your own code compiles against the stable API JAR only. The implementation and JSON serializer are runtime dependencies, so upgrading them never requires recompiling your classes.
Storing the Signing Key Safely
Every JWT you issue must be signed. The signature is what prevents a client from tampering with the payload. For HS256 (HMAC-SHA-256) you need a secret key of at least 256 bits (32 bytes). Store it in your application.yml as a Base64-encoded string and inject it with @Value — never hard-code it in a class file.
openssl rand -base64 32 (or its equivalent) and store the output in your secrets manager or CI/CD environment variable, then inject it at runtime. A weak or predictable secret defeats the entire signature scheme.
Building the JwtService
Encapsulate all JWT logic in a single @Service class. This keeps the rest of your application ignorant of the underlying library.
Keys.hmacShaKeyFor()? This factory method validates that the raw byte array is long enough for the requested algorithm before creating the key object. If you pass a key that is too short, it throws a WeakKeyException at startup — exactly when you want to catch misconfiguration, not at runtime on the first request.
What Happens Inside extractAllClaims
When you call Jwts.parser().verifyWith(signingKey).build().parseSignedClaims(token), JJWT does the following in order:
- Splits the token on the two
.separators into header, payload, and signature. - Base64url-decodes the header and payload JSON.
- Recomputes the HMAC-SHA256 over
base64url(header).base64url(payload)using your signing key. - Compares the result with the signature in the token (constant-time comparison to resist timing attacks).
- Checks standard claims:
exp(not expired),nbf(not used before, if present),iss/aud(if you configured them). - Returns the
Claimsmap, or throws a subclass ofJwtException.
Every exception subclass carries precise information: ExpiredJwtException, SignatureException, MalformedJwtException, UnsupportedJwtException. Your authentication filter (Lesson 4) will catch JwtException as the common parent and respond with 401 Unauthorized.
Adding Custom Claims
You can embed any JSON-serialisable value as an extra claim. A typical use case is embedding the user's roles so the authorisation layer can read them directly from the token without a database lookup:
Authorization header. Embedding large objects (full user profiles, long role lists) inflates every request. Stick to what the downstream service genuinely needs — username, role codes, tenant ID — and look up anything else from a cache or database on demand.
Asymmetric Keys: When to Switch from HS256 to RS256
HS256 uses a shared secret: every service that validates tokens must hold the same key, which means every service is also capable of issuing tokens. In a microservices architecture that is a large blast radius if any service is compromised.
RS256 (RSA-SHA256) splits this into a private key (only the auth server ever touches it) and a public key (distributed freely to resource servers). A resource server that can only verify tokens cannot forge them. Switching in JJWT requires generating an RSAPrivateKey/RSAPublicKey pair and calling signWith(privateKey) on generation and verifyWith(publicKey) on parsing. Spring Security's OAuth2 Resource Server support (Lesson 9) can serve the public key through a standard /.well-known/jwks.json endpoint automatically.
Testing Your JwtService
The service is a plain Spring bean — no web layer required. A fast unit test covers the critical paths:
Summary
You now have a production-ready JwtService that generates signed tokens with configurable expiry and custom claims, and validates them through JJWT's parser — which handles signature verification, expiry checking, and structured error reporting in one call. The next lesson wires this service into a Spring Security filter so that every incoming request is authenticated automatically before it reaches a controller.