Na continuação da nossa série sobre autenticação no Spring Boot, vamos implementar o serviço responsável por gerar e validar tokens JWT. Essa etapa é fundamental para garantir que apenas usuários autenticados tenham acesso às funcionalidades protegidas da aplicação.

🎯 Objetivo

Neste artigo, vamos:

  • Criar o serviço JwtService;
  • Entender como funcionam o secret key e o tempo de expiração do token;
  • Implementar a geração e validação dos tokens JWT;
  • Fazer as configurações necessárias no application.properties.

📦 Dependências do pom.xml para JWT com Spring Boot

É essencial que o artigo contenha as dependências do pom.xml para que o leitor consiga seguir o passo a passo sem erros, principalmente se estiver começando.


🛠️ Dependências necessárias no pom.xml

Para utilizar JWT com o Spring Boot, você precisa adicionar as bibliotecas abaixo ao seu pom.xml:

<!-- Spring Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>

<!-- JWT da biblioteca jjwt -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId> <!-- ou jjwt-gson -->
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>

📝 Observação

Você pode adaptar a versão 0.11.5 conforme a versão do seu projeto, mas essa é a mais utilizada atualmente com o novo formato de dependência modular do jjwt.


🛠️ Criação do JwtService

O JwtService é a classe responsável por gerar, extrair e validar tokens. Ele usa a biblioteca jjwt, que é compatível com o Spring Boot e fácil de configurar.


@Service
public class JwtService {
    @Value("${jwt.secret}")
    private String secret;
    @Value("${jwt.expiration}")
    private long expiration;
    public String generateToken(String username) {
        return Jwts.builder()
                .setSubject(username)
                .setIssuedAt(new Date(System.currentTimeMillis()))
                .setExpiration(new Date(System.currentTimeMillis() + expiration))
                .signWith(getSigningKey(), SignatureAlgorithm.HS256)
                .compact();
    }
    public String extractUsername(String token) {
        return extractClaim(token, Claims::getSubject);
    }
    public boolean isTokenValid(String token, String username) {
        final String extractedUsername = extractUsername(token);
        return extractedUsername.equals(username) && !isTokenExpired(token);
    }
    private boolean isTokenExpired(String token) {
        return extractClaim(token, Claims::getExpiration).before(new Date());
    }
    private <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
        final Claims claims = Jwts.parserBuilder()
                .setSigningKey(getSigningKey())
                .build()
                .parseClaimsJws(token)
                .getBody();
        return claimsResolver.apply(claims);
    }
    private Key getSigningKey() {
        return Keys.hmacShaKeyFor(secret.getBytes());
    }
}

🔐 Explicando o uso do Secret e Expiration

No application.properties, definimos as configurações de segurança:

# application.properties
jwt.secret=segredo-super-seguro-de-no-minimo-32-caracteres
jwt.expiration=86400000
  • jwt.secret: É a chave usada para assinar os tokens. Ela deve ter no mínimo 256 bits para garantir segurança com o algoritmo HS256.
  • jwt.expiration: Tempo de expiração do token, em milissegundos. No exemplo, usamos 24 horas (86400000 ms).

Essas propriedades são injetadas na nossa classe com @Value.


📥 Geração e validação do token

  • generateToken(String username): Cria um token com o nome do usuário como subject.
  • extractUsername(String token): Lê o subject contido no token.
  • isTokenValid(String token, String username): Verifica se o token pertence ao usuário informado e se ainda está válido.

Esses métodos são fundamentais para autenticar o usuário em requisições futuras.


🧠 Entendendo o JWT

Um JWT (JSON Web Token) é uma string codificada que representa uma identidade de forma segura. Ele possui três partes:

HEADER.PAYLOAD.SIGNATURE

Nós usaremos a biblioteca JJWT (Java JWT) da própria comunidade do Java para criar e verificar tokens.


🔍 Análise do Código

Vamos examinar a classe JwtService passo a passo:


@Service
public class JwtService {

A anotação @Service informa ao Spring que essa classe é um componente de serviço, permitindo sua injeção com @Autowired.


🔑 Propriedades de configuração


@Value("${jwt.secret}")
private String secret;
@Value("${jwt.expiration}")
private long expiration;

Essas propriedades serão lidas do application.properties. Por exemplo:

jwt.secret=chave-super-secreta-com-mais-de-32-caracteres
jwt.expiration=86400000 # 24 horas em milissegundos
  • secret é a chave usada para assinar o token.
  • expiration define quanto tempo o token será válido (em milissegundos).

🔐 Gerando o token


public String generateToken(String username) {
    return Jwts.builder()
        .setSubject(username)
        .setIssuedAt(new Date(System.currentTimeMillis()))
        .setExpiration(new Date(System.currentTimeMillis() + expiration))
        .signWith(getSigningKey(), SignatureAlgorithm.HS256)
        .compact();
}

Aqui estamos:

  • Definindo o usuário que será o “dono” do token (setSubject(username)).
  • Informando a data de criação do token.
  • Estabelecendo o prazo de validade.
  • Assinando o token com nossa chave secreta.
  • Retornando uma string compactada, que é o nosso JWT.

🧬 Extraindo o nome de usuário


public String extractUsername(String token) {
    return extractClaim(token, Claims::getSubject);
}

Essa função extrai o “subject” do token — que, nesse caso, representa o nome de usuário. Ela faz isso chamando a função auxiliar extractClaim.


🔍 O que é um “claim”?

Na estrutura de um token JWT, os claims são as informações que o token carrega. Eles são divididos em três tipos:

  • Registered claims (padrão): como sub (subject), iss (issuer), exp (expiration).
  • Public claims: definidas publicamente e registradas na IANA.
  • Private claims: criadas de forma personalizada por quem gera o token.

No nosso exemplo, usamos o claim "sub" (subject), que é o nome de usuário, mas poderíamos extrair outros, como a data de expiração (exp), o emissor (iss) e até claims personalizados como role, userId, etc.

🔍 Extraindo qualquer dado (claim)


private <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
    final Claims claims = Jwts.parserBuilder()
        .setSigningKey(getSigningKey())
        .build()
        .parseClaimsJws(token)
        .getBody();
    return claimsResolver.apply(claims);
}

Essa função genérica permite extrair qualquer informação (claim) do token. No caso do nome de usuário, usamos Claims::getSubject.


❌ Verificando se o token expirou


private boolean isTokenExpired(String token) {
    return extractClaim(token, Claims::getExpiration).before(new Date());
}

Essa função verifica se a data de expiração do token já passou.


✅ Verificando se o token é válido


public boolean isTokenValid(String token, String username) {
    final String extractedUsername = extractUsername(token);
    return extractedUsername.equals(username) && !isTokenExpired(token);
}

Um token JWT (JSON Web Token) é composto por três partes separadas por ponto (.):

HEADER>.<PAYLOAD>.<SIGNATURE>

🧱 Exemplo real de token JWT (fictício, mas com estrutura real):

Você pode usar o site https://jwt.io/, para visualizar o conteúdo do token.

eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhZG1pbiIsImV4cCI6MTY5MjM0OTYwMH0.qmGp8m5s3E_2GdZwV6Z4CgLdspjC5Jre9GoR0mfL4NQ

🔍 Detalhando cada parte:

1. Header (Cabeçalho)

{
"alg": "HS256",
"typ": "JWT"
}
  • Define o algoritmo de assinatura (ex: HS256)
  • Tipo do token: JWT

2. Payload (Corpo com os claims)

{
"sub": "admin",
"exp": 1692349600
}

Aqui ficam os claims, ou seja, as informações declaradas dentro do token.

📌 Exemplos de claims comuns:

  • sub: subject (usuário ou identificador único) → "admin"
  • exp: expiration (tempo de expiração, em timestamp Unix)
  • iat: issued at (quando foi gerado)
  • roles: permissões do usuário (caso você deseje incluir)

Claim significa literalmente “declaração”. No JWT, é uma afirmação sobre um dado (como quem é o usuário ou quando o token expira).

3. Signature (Assinatura digital)

É gerada usando:

HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
secret
)

Garante que ninguém alterou o conteúdo do token.


✅ Exemplo prático de um claim:

{
"sub": "kanechan",
"role": "ADMIN",
"exp": 1734567890
}

Neste caso:

  • sub → diz quem é o dono do token.
  • role → diz que ele é um administrador.
  • exp → define quando o token expira.

Aqui validamos se:

  • O usuário extraído do token é igual ao usuário autenticado.
  • O token ainda não expirou.

🔒 Gerando a chave de assinatura

private Key getSigningKey() {
return Keys.hmacShaKeyFor(secret.getBytes());
}

Essa função transforma a string secreta em uma chave do tipo Key para ser usada nas assinaturas dos tokens.


🛠️ Estrutura de Pacotes

Essa classe está no pacote:

com.kanechan.restaurante.services

Como é um serviço de autenticação, o pacote services é o local mais apropriado.

✅ Conclusão

Com essa etapa, finalizamos a construção do serviço de autenticação JWT. Agora nossa aplicação consegue gerar tokens seguros, verificar se estão válidos e extrair o usuário autenticado a partir deles.

No próximo artigo, vamos conectar isso tudo ao filtro JwtAuthenticationFilter, que irá interceptar as requisições HTTP e garantir que apenas usuários autenticados tenham acesso.


🔜 Próximo artigo:

Como Proteger Endpoints com Filtro JWT no Spring Boot (Parte 3)

Categorized in:

Backend, Spring Boot,