java 150 lines · 3 tabs

Stateless JWT Authentication with a Token Service and a Spring Security Filter

Shared by codesnips Sep 2026
3 tabs
package com.example.security;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.stereotype.Service;

import javax.crypto.SecretKey;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;

@Service
public class JwtTokenService {

    private final SecretKey key;
    private final long ttlSeconds;

    public JwtTokenService(@Value("${jwt.secret}") String secret,
                           @Value("${jwt.ttl-seconds:900}") long ttlSeconds) {
        this.key = Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8));
        this.ttlSeconds = ttlSeconds;
    }

    public String generate(String username, List<String> roles) {
        Instant now = Instant.now();
        return Jwts.builder()
                .subject(username)
                .claim("roles", roles)
                .issuedAt(Date.from(now))
                .expiration(Date.from(now.plusSeconds(ttlSeconds)))
                .signWith(key)
                .compact();
    }

    public Claims parse(String token) {
        return Jwts.parser()
                .verifyWith(key)
                .build()
                .parseSignedClaims(token)
                .getPayload();
    }

    @SuppressWarnings("unchecked")
    public Authentication authenticate(Claims claims) {
        List<String> roles = claims.get("roles", List.class);
        List<GrantedAuthority> authorities = roles.stream()
                .map(SimpleGrantedAuthority::new)
                .collect(Collectors.toList());
        return new UsernamePasswordAuthenticationToken(claims.getSubject(), null, authorities);
    }
}
3 files · java Explain with highlit

This snippet shows how stateless authentication is wired together in a Spring Boot application: a service that signs and verifies JSON Web Tokens, and a servlet filter that pulls the token off each request and populates the security context. Because the tokens are self-contained, the server keeps no session state — every request carries its own proof of identity, which is what makes horizontal scaling and cross-service auth practical.

In JwtTokenService, the signing key is derived once from a configured secret with Keys.hmacShaKeyFor, and reused for both signing and verification. The generate method builds a compact HS256 token with the subject set to the username, a roles claim, and explicit issuedAt/expiration timestamps. Putting the expiry inside the signed payload is the whole point of statelessness: the server does not need to look anything up to know when a token dies. The parse method verifies the signature and expiry in one step via parseSignedClaims; any tampering, wrong key, or expired token raises a JwtException, which the caller treats as "not authenticated" rather than "error".

The authenticate method converts verified Claims into a Spring Authentication. It reads the roles claim, maps each to a SimpleGrantedAuthority, and returns a UsernamePasswordAuthenticationToken marked authenticated. Deriving authorities from the token — instead of re-querying the database — is a deliberate trade-off: it is fast and stateless, but it means role changes only take effect when a new token is issued, so short expiries matter.

In JwtAuthenticationFilter, extending OncePerRequestFilter guarantees the logic runs a single time per request even through forwards. It extracts the bearer token with resolveToken, and only sets SecurityContextHolder when parsing succeeds and nothing is already authenticated. Crucially, a failed parse does not reject the request here — it simply leaves the context empty and calls filterChain.doFilter, letting downstream authorization rules decide. This separation keeps the filter focused on identity, not policy.

A pitfall worth noting: the secret must be long enough for HS256 (256 bits), and the filter must be registered before the default UsernamePasswordAuthenticationFilter in the security config for the context to be populated in time.


Related snips

ruby
payload = {
  sub: user.id,
  iss: 'https://auth.example.com',
  aud: 'codesnips-api',
  exp: 15.minutes.from_now.to_i,
  iat: Time.now.to_i,

JWT issuance and verification without common footguns

jwt authentication api
by Kai Nakamura 2 tabs
ruby
timestamp = request.headers.fetch('X-Signature-Timestamp')
signature = request.headers.fetch('X-Signature')
payload = request.raw_post

data = "#{timestamp}.#{payload}"
expected = OpenSSL::HMAC.hexdigest('SHA256', ENV.fetch('WEBHOOK_SECRET'), data)

HMAC signed API requests for webhook and partner integrity

hmac api-signing webhooks
by Kai Nakamura 2 tabs
bash
#!/usr/bin/env bash
set -euo pipefail

export VAULT_ADDR="https://vault.internal:8200"
export VAULT_TOKEN="${VAULT_TOKEN:?missing VAULT_TOKEN}"

Secrets management with environment isolation and Vault

secrets-management vault environment-variables
by Kai Nakamura 1 tab
typescript
import { randomBytes, createHash } from "crypto";
import jwt from "jsonwebtoken";
import { RefreshTokenStore } from "./store";

const ACCESS_SECRET = process.env.ACCESS_SECRET!;
const ACCESS_TTL = "15m";

JWT access + refresh token rotation (conceptual)

security node jwt
by codesnips 3 tabs
go
package files

import (
  "context"
  "time"

Presigned S3 upload URLs (AWS SDK v2)

go aws s3
by Leah Thompson 1 tab
erb
<%# private stream: turbo signs the serialized record name %>
<%= turbo_stream_from current_user %>

<section class="notifications">
  <h1>Notifications</h1>

Turbo Streams + authorization: signed per-user stream name

rails turbo hotwire
by codesnips 3 tabs

Share this code

Here's the card — post it anywhere.

Stateless JWT Authentication with a Token Service and a Spring Security Filter — share card
Link copied