Especialização em Backend com Java & Spring Boot • Spring Boot 3 & Java 21 LTS • Spring Data JPA, Security, Microsserviços e Cloud


🗺️ Mapa Conceitual do Tópico

flowchart TD
    A["Cliente HTTP / Frontend"] --> B["API Gateway / Router"]
    B --> C["Controller / Handler"]
    C --> D["Service Layer (Regras de Negócio)"]
    D --> E["Repository / ORM (Persistência)"]
    E --> F["Banco de Dados / Cache"]

    subgraph ARQ["Arquitetura do Capítulo"]
        G["Conceito: Autenticação Stateless com JWT e Filtros Customizados"]
        H["Segurança, Validação e Resiliência"]
        I["Alta Performance e Escalabilidade"]
    end

    D --> ARQ

    style A fill:#e1f5fe,stroke:#03a9f4,stroke-width:2px
    style B fill:#fff3e0,stroke:#ff9800,stroke-width:2px
    style C fill:#ede7f6,stroke:#7e57c2,stroke-width:2px
    style D fill:#e8f5e9,stroke:#4caf50,stroke-width:2px
    style E fill:#fce4ec,stroke:#e91e63,stroke-width:2px
    style F fill:#f3e5f5,stroke:#9c27b0,stroke-width:2px

🏛️ 1. Fundamentos Técnicos de Autenticação Stateless com JWT e Filtros Customizados

Por que JWT em vez de sessão. Em uma API stateless (Capítulo 09 já configurou SessionCreationPolicy.STATELESS), o servidor não guarda nenhum estado de autenticação entre requisições — não existe HttpSession nem armazenamento server-side do “quem está logado”. O JWT (JSON Web Token) resolve isso transportando a própria prova de identidade no lado do cliente: um token auto-contido, composto por três partes separadas por ponto (header.payload.signature), codificadas em Base64URL. O payload carrega claimssub (subject/usuário), iat (issued at), exp (expiration), papéis/roles — e a signature garante que ninguém alterou esse conteúdo sem possuir a chave secreta.

Geração do token. A biblioteca jjwt (io.jsonwebtoken) constrói o token com uma API fluente: Jwts.builder().subject(...).issuedAt(...).expiration(...).signWith(chave).compact(). A assinatura pode ser simétrica (HMAC-SHA256, uma única chave secreta compartilhada entre quem emite e quem valida — mais simples, mas exige que o segredo circule entre serviços) ou assimétrica (RSA/EC, par de chaves pública/privada — o emissor assina com a privada, qualquer serviço valida com a pública, sem nunca compartilhar segredo; é o modelo usado por OAuth2/OpenID Connect via JWKS).

O filtro de autenticação por requisição. Como não há sessão, cada requisição precisa provar sua identidade novamente — isso é feito por um filtro Servlet customizado que herda de OncePerRequestFilter, garantindo execução única por requisição mesmo em cenários de forward/include internos. O filtro é registrado na SecurityFilterChain antes do UsernamePasswordAuthenticationFilter padrão (via addFilterBefore), intercepta o header Authorization: Bearer <token>, valida assinatura e expiração e, se tudo estiver correto, monta um UsernamePasswordAuthenticationToken com o usuário e suas GrantedAuthoritys, registrando-o em SecurityContextHolder.getContext().setAuthentication(...). Só então a requisição segue para o restante da cadeia — se o token for inválido ou ausente, o filtro simplesmente deixa a requisição prosseguir sem autenticação, e é a etapa de authorizeHttpRequests que decide se aquele endpoint exige login (retornando 401 via AuthenticationEntryPoint, como visto no capítulo anterior).

Refresh token e rotação. Tokens de acesso (access tokens) devem ter vida curta (minutos a poucas horas) para limitar o estrago de um vazamento — mas isso obrigaria o usuário a logar de novo com frequência. A solução é um segundo token, o refresh token, com vida mais longa e escopo único (trocar-se por um novo access token em um endpoint dedicado, ex. /auth/refresh). Uma prática de segurança importante é a rotação: a cada uso do refresh token, um novo é emitido e o anterior é invalidado (geralmente registrado em banco/Redis com um identificador único, o jti), permitindo detectar reuso indevido — se um refresh token já usado aparecer de novo, é sinal de que foi roubado, e toda a cadeia de tokens daquela sessão pode ser revogada.

Gotcha comum: logout stateless. Como o servidor não guarda sessão, “invalidar” um JWT antes do seu exp natural não é trivial — não existe um session.invalidate(). As soluções usuais são: manter uma blocklist de tokens revogados (por jti) em Redis com TTL igual ao tempo restante do token, ou aceitar que o token de acesso viva pouco e delegar o controle de revogação ao refresh token (que aí sim é persistido e pode ser apagado no logout).


💻 2. Código de Demonstração Corporativo

package com.empresa.api.security;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import javax.crypto.SecretKey;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Date;
import java.util.List;

@Service
public class TokenService {

    private final SecretKey chave;

    public TokenService(@Value("${jwt.secret}") String segredo) {
        this.chave = Keys.hmacShaKeyFor(segredo.getBytes());
    }

    public String gerarToken(String username, List<String> papeis) {
        return Jwts.builder()
            .subject(username)
            .claim("papeis", papeis)
            .issuedAt(Date.from(Instant.now()))
            .expiration(Date.from(Instant.now().plus(15, ChronoUnit.MINUTES)))
            .signWith(chave)
            .compact();
    }

    public Claims validarEExtrairClaims(String token) {
        // Lança JwtException (assinatura inválida) ou ExpiredJwtException (expirado)
        return Jwts.parser()
            .verifyWith(chave)
            .build()
            .parseSignedClaims(token)
            .getPayload();
    }
}
package com.empresa.api.security;

import io.jsonwebtoken.JwtException;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.lang.NonNull;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;
import java.util.List;

@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {

    private final TokenService tokenService;

    public JwtAuthenticationFilter(TokenService tokenService) {
        this.tokenService = tokenService;
    }

    @Override
    protected void doFilterInternal(@NonNull HttpServletRequest request,
                                     @NonNull HttpServletResponse response,
                                     @NonNull FilterChain chain) throws ServletException, IOException {
        String header = request.getHeader("Authorization");

        if (header != null && header.startsWith("Bearer ")) {
            try {
                var claims = tokenService.validarEExtrairClaims(header.substring(7));
                List<String> papeis = claims.get("papeis", List.class);
                var authorities = papeis.stream().map(SimpleGrantedAuthority::new).toList();

                var authentication = new UsernamePasswordAuthenticationToken(
                    claims.getSubject(), null, authorities);
                SecurityContextHolder.getContext().setAuthentication(authentication);
            } catch (JwtException e) {
                // Token inválido/expirado: contexto permanece anônimo; authorizeHttpRequests decide o 401.
                SecurityContextHolder.clearContext();
            }
        }

        chain.doFilter(request, response);
    }
}

🔗 Recursos Pedagógicos do Capítulo 10

Recurso Didático Finalidade Link de Acesso
📊 Slides de Aula Apresentação visual interativa com Dark Mode e suporte a teclado Ver Slides
🧠 Quiz Formativo Teste interativo de fixação com feedback imediato por alternativa Fazer Quiz
💻 Exemplos de Código Demonstrações funcionais com código executável Ver Exemplos
🧩 Exercícios em 4 Níveis Lista progressiva de fixação com gabarito em bloco colapsável Resolver Exercícios

⬅️ Capítulo Anterior 📚 Sumário de Tópicos Próximo Capítulo ➡️