Especialização em Backend com Java & Spring Boot • Demonstrações Práticas Compiláveis


💻 Código de Exemplo 01 — Endpoint de Login com AuthenticationManager e Emissão de Token

package com.empresa.api.controller;

import com.empresa.api.security.TokenService;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/api/v1/auth")
public class AuthController {

    record LoginRequest(String email, String senha) {}
    record TokenResponse(String accessToken, String tipo, long expiraEmSegundos) {}

    private final AuthenticationManager authenticationManager;
    private final TokenService tokenService;

    public AuthController(AuthenticationManager authenticationManager, TokenService tokenService) {
        this.authenticationManager = authenticationManager;
        this.tokenService = tokenService;
    }

    @PostMapping("/login")
    public ResponseEntity<TokenResponse> login(@RequestBody LoginRequest request) {
        // AuthenticationManager delega a um DaoAuthenticationProvider que consulta
        // UserDetailsService e compara a senha com PasswordEncoder.matches(...).
        Authentication authentication = authenticationManager.authenticate(
            new UsernamePasswordAuthenticationToken(request.email(), request.senha()));

        List<String> papeis = authentication.getAuthorities().stream()
            .map(GrantedAuthority::getAuthority)
            .toList();

        String token = tokenService.gerarToken(authentication.getName(), papeis);
        return ResponseEntity.ok(new TokenResponse(token, "Bearer", 900));
    }
}

💻 Código de Exemplo 02 — Registro do Filtro JWT na SecurityFilterChain

package com.empresa.api.config;

import com.empresa.api.security.JwtAuthenticationFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
public class SecurityConfig {

    private final JwtAuthenticationFilter jwtAuthenticationFilter;

    public SecurityConfig(JwtAuthenticationFilter jwtAuthenticationFilter) {
        this.jwtAuthenticationFilter = jwtAuthenticationFilter;
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        return http
            .csrf(csrf -> csrf.disable())
            .sessionManagement(s -> s.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/api/v1/auth/**").permitAll()
                .anyRequest().authenticated()
            )
            // O filtro JWT roda ANTES do filtro padrão de usuário/senha do Spring Security.
            .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
            .build();
    }
}

💻 Código de Exemplo 03 — Refresh Token com Rotação e Persistência em Redis

package com.empresa.api.security;

import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.time.Duration;
import java.util.UUID;

@Service
public class RefreshTokenService {

    private static final Duration TTL = Duration.ofDays(7);
    private final StringRedisTemplate redisTemplate;

    public RefreshTokenService(StringRedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    public String emitir(String usuario) {
        String jti = UUID.randomUUID().toString();
        redisTemplate.opsForValue().set("refresh:" + jti, usuario, TTL);
        return jti;
    }

    /**
     * Rotação: o refresh token usado é imediatamente descartado e um novo é emitido.
     * Se o mesmo jti for apresentado de novo, ele já não existirá na chave Redis
     * (retorna null) — sinal de possível roubo/reuso do token.
     */
    public String rotacionar(String jtiAntigo) {
        String usuario = redisTemplate.opsForValue().get("refresh:" + jtiAntigo);
        if (usuario == null) {
            throw new IllegalStateException("Refresh token inválido, expirado ou já utilizado");
        }
        redisTemplate.delete("refresh:" + jtiAntigo);
        return emitir(usuario);
    }

    public void revogar(String jti) {
        redisTemplate.delete("refresh:" + jti);
    }
}