How do you mint a signed JWT in Spring using JwtEncoder/NimbusJwtEncoder? Walk through the JWK source, JwsHeader, JwtClaimsSet, and encode call.
answer
- JWKSource<SecurityContext> holds signing keys
- JwtClaimsSet.builder(): iss/sub/aud/iat/exp/claim
- JwsHeader picks SignatureAlgorithm (RS256)
- encode(JwtEncoderParameters.from(header, claims))
- getTokenValue() = compact JWT string; signed not encrypted
basics
~20 sCreate a NimbusJwtEncoder backed by a JWKSource holding your signing key. Build a JwtClaimsSet (issuer, subject, audience, issuedAt, expiresAt, custom claims) and a JwsHeader (algorithm), then call jwtEncoder.encode(JwtEncoderParameters.from(header, claims)) to get a signed Jwt whose getTokenValue() is the JWT string.
solid answer
~40 s`JwtEncoder` is Spring Security's abstraction for producing signed JWTs; `NimbusJwtEncoder` is the Nimbus-backed implementation. You construct it with a `JWKSource<SecurityContext>` — the pool of JSON Web Keys (usually an RSA or EC key pair) it signs with. To mint a token you build a `JwtClaimsSet` via its builder (`issuer`, `subject`, `audience`, `issuedAt`, `expiresAt`, `id`, plus arbitrary `claim(name, value)` entries), optionally a `JwsHeader` selecting the signature algorithm (e.g. `SignatureAlgorithm.RS256`), and call `encoder.encode(JwtEncoderParameters.from(header, claims))`. It selects a matching key from the JWK source, signs, and returns a `Jwt` whose `getTokenValue()` is the compact serialized string. If you omit the header, it infers the algorithm from the key. This is the standard way to issue app-minted access tokens (e.g. for a lightweight token endpoint) without a full authorization server.
code
java · 37 lines@Configuration
class JwtIssueConfig {
@Bean
JWKSource<SecurityContext> jwkSource(KeyPair keyPair) {
RSAKey rsaKey = new RSAKey.Builder((RSAPublicKey) keyPair.getPublic())
.privateKey(keyPair.getPrivate())
.keyID(UUID.randomUUID().toString())
.build();
return new ImmutableJWKSet<>(new JWKSet(rsaKey));
}
@Bean
JwtEncoder jwtEncoder(JWKSource<SecurityContext> jwkSource) {
return new NimbusJwtEncoder(jwkSource);
}
}
@Service
class TokenService {
private final JwtEncoder encoder;
TokenService(JwtEncoder encoder) { this.encoder = encoder; }
String mint(String userId) {
Instant now = Instant.now();
JwtClaimsSet claims = JwtClaimsSet.builder()
.issuer("https://auth.example.com")
.subject(userId)
.audience(List.of("https://api.example.com"))
.issuedAt(now)
.expiresAt(now.plus(1, ChronoUnit.HOURS))
.claim("scope", "read write")
.build();
JwsHeader header = JwsHeader.with(SignatureAlgorithm.RS256).build();
return encoder.encode(JwtEncoderParameters.from(header, claims)).getTokenValue();
}
}go deeper
Knows JwtEncoder produces a signed JWT string.
Can build claims/header and call encode with a JWKSource.
Understands key types, algorithm selection, and the encoder/decoder-via-JWKS contract.
Designs key rotation, kid strategy, algorithm policy (RSA/EC/HMAC), and when to use JwtEncoder vs a full authorization server.
## The abstraction `JwtEncoder` (interface) mints signed JWTs: `Jwt encode(JwtEncoderParameters parameters)`. The production implementation is **`NimbusJwtEncoder`**, using the Nimbus JOSE + JWT library under the hood. (There is a matching `JwtDecoder`/`NimbusJwtDecoder` for the verification side.) ## Step 1 — the signing key: JWKSource `NimbusJwtEncoder` is constructed with a **`JWKSource<SecurityContext>`** — a supplier of JSON Web Keys (JWKs). A JWK wraps a cryptographic key plus metadata (key id `kid`, algorithm, use). For asymmetric signing you build an `RSAKey` (or `ECKey`) from a key pair, give it a `keyID`, put it in a `JWKSet`, and wrap it in an `ImmutableJWKSet`: ```java RSAKey rsaKey = new RSAKey.Builder(publicKey) .privateKey(privateKey) .keyID(UUID.randomUUID().toString()) .build(); JWKSource<SecurityContext> jwks = new ImmutableJWKSet<>(new JWKSet(rsaKey)); JwtEncoder encoder = new NimbusJwtEncoder(jwks); ``` The encoder later exposes the public half at a JWK Set endpoint so resource servers can verify. The private key never leaves the issuer. ## Step 2 — the claims: JwtClaimsSet `JwtClaimsSet.builder()` assembles the payload: - `issuer(String)` → `iss` - `subject(String)` → `sub` - `audience(List<String>)` → `aud` - `issuedAt(Instant)` → `iat` - `expiresAt(Instant)` → `exp` - `notBefore(Instant)` → `nbf` - `id(String)` → `jti` - `claim(String, Object)` → any custom claim (e.g. `scope`, roles) These are exactly the claims the validators on the other side inspect (`exp`/`nbf` → `JwtTimestampValidator`, `iss` → `JwtIssuerValidator`, `aud` → your audience validator), so encoder and validators are two ends of the same contract. ## Step 3 — the header: JwsHeader `JwsHeader.with(SignatureAlgorithm.RS256).build()` picks the JWS signature algorithm and lets you set header params (e.g. `kid`, `type`). The algorithm must be compatible with the key type (RS256/RS384/RS512 or PS* for RSA; ES256/384/512 for EC). If you don't supply a header, the encoder infers a suitable algorithm from the selected JWK. ## Step 4 — encode ```java Jwt jwt = encoder.encode(JwtEncoderParameters.from(header, claims)); String token = jwt.getTokenValue(); // compact 'header.payload.signature' ``` `JwtEncoderParameters.from(claims)` (claims only) or `from(header, claims)` (both). The encoder selects a matching key from the `JWKSource`, signs the header+payload, and returns a `Jwt` object (same type the decoder produces), so you can read back claims and the serialized value. ## When to use it - A lightweight, self-hosted token/login endpoint that issues its own access tokens. - Service-to-service tokens, short-lived signed assertions, or client-assertion JWTs. - Spring Authorization Server uses this machinery internally to mint tokens. Don't hand-roll JWT signing with raw libraries when `JwtEncoder` already integrates key management, algorithm selection, and the `Jwt` model. ## Gotchas - **Algorithm/key mismatch** → `JwtEncodingException`. RS256 needs an RSA key; ES256 needs EC. - **Symmetric (HMAC) signing** uses an `OctetSequenceKey` and HS256; fine for single-service verify-with-shared-secret, but you can't publish a private JWK set for others. - **Key rotation**: put multiple keys in the `JWKSet` and select by `kid`; the resource server fetches all public keys from the JWK Set URI. - **Instants matter**: always set `issuedAt` and `expiresAt`; a token with no `exp` won't be expiry-checked by the default validator. - `NimbusJwtEncoder` is thread-safe; register it as a singleton bean. - Encoding is signing, not encryption — claims are readable (base64url), so don't put secrets in them.
- What is the JWKSource's role, and how does the resource server verify tokens you mint?The JWKSource supplies the private signing key(s) to the encoder. You expose the corresponding public keys at a JWK Set URI; the resource server's NimbusJwtDecoder fetches them (matching by kid) to verify the signature.
- If you omit the JwsHeader in JwtEncoderParameters, what happens?The encoder infers the signature algorithm from the selected JWK (e.g. RS256 for an RSA key) and builds a default header. Passing a header lets you pin the algorithm or set extra header params like kid/type.
saying these in an interview costs you the question
- Thinking encode() encrypts the claims (it only signs; payload is readable)
- Pairing RS256 with an EC/HMAC key (algorithm/key mismatch throws)
- Putting secrets in claims because the token 'is signed'
- Forgetting expiresAt so the token is never expiry-validated
- Confusing JwtEncoder (mint) with JwtDecoder (verify)