📚 Módulo 05: Backend - API REST, CORS e Segurança com JWT
✅ Pré-Requisitos deste Módulo
Confirme antes de começar:
./mvnw clean compile # deve retornar BUILD SUCCESS
Você deve ter concluído os Módulos 01–04 (entidades, repositórios, DTOs, mappers e serviços de produto e pedido criados). A dependência spring-boot-starter-security deve estar ativa no pom.xml.
Neste módulo final do backend, uniremos todas as peças construídas. Desenvolveremos os controladores REST (Controllers), exporemos os endpoints de catálogo e vendas, lidaremos com a configuração segura de CORS para conectar o frontend hospedado na Netlify e blindaremos a API utilizando a especificação stateless do Spring Security baseada em Tokens JWT (JSON Web Tokens).
🌎 1. O que é CORS e por que configurá-lo?
CORS (Cross-Origin Resource Sharing) é um mecanismo de segurança implementado nativamente pelos navegadores. Ele impede que uma aplicação SPA (hospedada em http://tecloja.netlify.app) leia dados de uma API em outra origem (https://backend.render.com) sem que a API dê permissão expressa.
Para habilitar essa comunicação de forma profissional, integraremos a configuração de CORS diretamente nas diretivas de segurança do Spring Security, garantindo que requisições do Angular local (porta 4200) e de produção (Netlify) sejam aceitas com segurança.
🔒 2. A Mágica do Spring Security Stateless com JWT
Em APIs modernas (REST), não guardamos sessões no servidor (Stateless). Cada requisição HTTP deve conter um selo digital criptográfico chamado Token JWT anexado no cabeçalho Authorization: Bearer <TOKEN>.
Arquitetura de Componentes da Segurança (Mermaid)
flowchart TD
Req["1. Requisição HTTP externa<br>POST /api/v1/pedidos"] --> Filter["2. JwtAuthenticationFilter<br>(Extrai e valida Token JWT)"]
Filter -- "Se válido" --> Auth["3. SecurityContextHolder<br>(Injeta Autenticação do Usuário)"]
Auth --> Controller["4. RestController<br>(Executa endpoint protegido)"]
Filter -- "Se inválido/vazio" --> Deny["5. Retorna HTTP 401 Unauthorized"]
☕ 3. Código Fonte Completo da Segurança
Crie os arquivos a seguir no pacote correspondente:
1. Provedor de Tokens (br.com.tecloja.api.config.security.JwtTokenProvider.java)
Gerencia o ciclo de geração, leitura e assinatura criptográfica dos tokens.
package br.com.tecloja.api.config.security;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import java.security.Key;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
@Component
public class JwtTokenProvider {
@Value("${tecloja.jwt.secret}")
private String jwtSecret;
@Value("${tecloja.jwt.expiration}")
private long jwtExpiration;
private Key getSigningKey() {
return Keys.hmacShaKeyFor(jwtSecret.getBytes());
}
public String generateToken(String username, String role) {
Map<String, Object> claims = new HashMap<>();
claims.put("role", role);
return Jwts.builder()
.setClaims(claims)
.setSubject(username)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + jwtExpiration))
.signWith(getSigningKey(), SignatureAlgorithm.HS256)
.compact();
}
public String getUsernameFromToken(String token) {
return getClaimFromToken(token, Claims::getSubject);
}
public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
final Claims claims = Jwts.parserBuilder()
.setSigningKey(getSigningKey())
.build()
.parseClaimsJws(token)
.getBody();
return claimsResolver.apply(claims);
}
public boolean validateToken(String token, UserDetails userDetails) {
final String username = getUsernameFromToken(token);
return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
}
private boolean isTokenExpired(String token) {
final Date expiration = getClaimFromToken(token, Claims::getExpiration);
return expiration.before(new Date());
}
}
2. Filtro do Protocolo de Autenticação (br.com.tecloja.api.config.security.JwtAuthenticationFilter.java)
O “segurança” da API. Intercepta todas as chamadas HTTP e injeta o usuário autenticado caso o token no cabeçalho seja válido.
package br.com.tecloja.api.config.security;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtTokenProvider jwtTokenProvider;
private final UserDetailsService userDetailsService;
public JwtAuthenticationFilter(JwtTokenProvider jwtTokenProvider, UserDetailsService userDetailsService) {
this.jwtTokenProvider = jwtTokenProvider;
this.userDetailsService = userDetailsService;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
final String authHeader = request.getHeader("Authorization");
final String jwt;
final String username;
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
filterChain.doFilter(request, response);
return;
}
jwt = authHeader.substring(7);
username = jwtTokenProvider.getUsernameFromToken(jwt);
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
if (jwtTokenProvider.validateToken(jwt, userDetails)) {
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities()
);
authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authToken);
}
}
filterChain.doFilter(request, response);
}
}
3. Integração com o Banco de Dados (br.com.tecloja.api.service.impl.DatabaseUserDetailsService.java)
Conecta a segurança do Spring com o banco relacional por meio de UsuarioRepository.
package br.com.tecloja.api.service.impl;
import br.com.tecloja.api.model.Usuario;
import br.com.tecloja.api.repository.UsuarioRepository;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.stream.Collectors;
@Service
public class DatabaseUserDetailsService implements UserDetailsService {
private final UsuarioRepository usuarioRepository;
public DatabaseUserDetailsService(UsuarioRepository usuarioRepository) {
this.usuarioRepository = usuarioRepository;
}
@Override
@Transactional(readOnly = true) // Garante sessão JPA ativa para carregar os papéis do usuário
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Usuario usuario = usuarioRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("Usuário não cadastrado: " + username));
return new User(
usuario.getUsername(),
usuario.getSenha(),
usuario.getPapeis().stream()
.map(papel -> new SimpleGrantedAuthority(papel.getNome()))
.collect(Collectors.toList())
);
}
}
4. Arquitetura Geral da Segurança (br.com.tecloja.api.config.SecurityConfig.java)
Aqui ativamos a segurança stateless, associamos o filtro de JWT e configuramos os acessos públicos e privados dos endpoints.
package br.com.tecloja.api.config;
import br.com.tecloja.api.config.security.JwtAuthenticationFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import java.util.Arrays;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
private final JwtAuthenticationFilter jwtAuthenticationFilter;
public SecurityConfig(JwtAuthenticationFilter jwtAuthenticationFilter) {
this.jwtAuthenticationFilter = jwtAuthenticationFilter;
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
.csrf(csrf -> csrf.disable()) // APIs stateless não necessitam de proteção CSRF
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(auth -> auth
// Rotas Públicas (liberados endpoints com prefixo de versão e H2 console)
.requestMatchers("/api/v1/auth/login", "/h2-console/**", "/swagger-ui/**", "/v3/api-docs/**").permitAll()
.requestMatchers(HttpMethod.GET, "/api/v1/produtos/**", "/api/v1/categorias/**").permitAll()
// Rotas de Venda (Apenas Cliente/Usuário com ROLE_USER pode faturar)
.requestMatchers("/api/v1/pedidos/**").hasRole("USER")
// Rotas Administrativas (Apenas ADMIN pode cadastrar, editar e excluir)
.requestMatchers(HttpMethod.POST, "/api/v1/produtos/**").hasRole("ADMIN")
.requestMatchers(HttpMethod.PUT, "/api/v1/produtos/**").hasRole("ADMIN")
.requestMatchers(HttpMethod.DELETE, "/api/v1/produtos/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
// sameOrigin: permite frames apenas da mesma origem (seguro para H2 console local)
.headers(headers -> headers.frameOptions(frame -> frame.sameOrigin()))
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
return config.getAuthenticationManager();
}
/**
* Fonte de configuração CORS usada diretamente pelo Spring Security 6.
* Registrada via .cors(cors -> cors.configurationSource(...)) — sem necessidade
* de um Bean CorsFilter separado (que causaria dupla interceptação).
*/
@Bean
public UrlBasedCorsConfigurationSource corsConfigurationSource() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
// Permite a origem de Dev local do Angular e subdomínios de produção da Netlify
config.setAllowedOriginPatterns(Arrays.asList("http://localhost:4200", "https://*.netlify.app"));
config.setAllowedHeaders(Arrays.asList("Origin", "Content-Type", "Accept", "Authorization"));
config.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS"));
source.registerCorsConfiguration("/**", config);
return source;
}
}
🎛️ 4. Os Controladores REST (Controllers)
[!NOTE] Boas Práticas de Mercado — Por que usamos Versionamento de API (
/api/v1)?Em ambientes corporativos de alto desempenho, as APIs evoluem rapidamente. Se alterarmos a estrutura de uma resposta ou o nome de um campo em um endpoint ativo, nós podemos “quebrar” aplicativos móveis ou outros sistemas clientes antigos que ainda não atualizaram para a nova versão.
Para evitar isso, a engenharia de software adota o Versionamento de API via URL. Ao utilizarmos o prefixo
/api/v1/, garantimos um contrato estável. No futuro, se precisarmos fazer alterações drásticas e disruptivas nas regras de negócio (como reestruturar o checkout), podemos criar a versão/api/v2/em paralelo sem desligar ou quebrar os sistemas antigos que continuam consumindo a/api/v1/.
Crie os controladores no pacote br.com.tecloja.api.controller:
1. AuthController.java (Login e Geração do Token)
package br.com.tecloja.api.controller;
import br.com.tecloja.api.config.security.JwtTokenProvider;
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.Map;
@RestController
@RequestMapping("/api/v1/auth")
public class AuthController {
private final AuthenticationManager authenticationManager;
private final JwtTokenProvider jwtTokenProvider;
public AuthController(AuthenticationManager authenticationManager, JwtTokenProvider jwtTokenProvider) {
this.authenticationManager = authenticationManager;
this.jwtTokenProvider = jwtTokenProvider;
}
@PostMapping("/login")
public Map<String, String> login(@RequestBody Map<String, String> request) {
String username = request.get("username");
String senha = request.get("senha");
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(username, senha)
);
// Captura o principal papel de segurança do usuário
String role = authentication.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.findFirst()
.orElse("ROLE_USER");
String token = jwtTokenProvider.generateToken(username, role);
// Retorna DTO de autenticação simplificado
return Map.of(
"username", username,
"role", role,
"token", token
);
}
}
2. ProdutoController.java (Endpoints Públicos e Administrativos)
package br.com.tecloja.api.controller;
import br.com.tecloja.api.dto.ProdutoDTO;
import br.com.tecloja.api.dto.ProdutoFormDTO;
import br.com.tecloja.api.service.ProdutoService;
import jakarta.validation.Valid;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/v1/produtos")
public class ProdutoController {
private final ProdutoService produtoService;
public ProdutoController(ProdutoService produtoService) {
this.produtoService = produtoService;
}
@GetMapping
public ResponseEntity<List<ProdutoDTO>> listarTodos() {
return ResponseEntity.ok(produtoService.listarTodos());
}
@GetMapping("/{id}")
public ResponseEntity<ProdutoDTO> buscarPorId(@PathVariable Long id) {
return ResponseEntity.ok(produtoService.buscarPorId(id));
}
@GetMapping("/categoria/{id}")
public ResponseEntity<List<ProdutoDTO>> buscarPorCategoria(@PathVariable Long id) {
return ResponseEntity.ok(produtoService.buscarPorCategoria(id));
}
@GetMapping("/pesquisa")
public ResponseEntity<List<ProdutoDTO>> pesquisarPorNome(@RequestParam String nome) {
return ResponseEntity.ok(produtoService.pesquisarPorNome(nome));
}
@PostMapping
public ResponseEntity<ProdutoDTO> criar(@Valid @RequestBody ProdutoFormDTO form) {
return ResponseEntity.status(HttpStatus.CREATED).body(produtoService.criar(form));
}
@PutMapping("/{id}")
public ResponseEntity<ProdutoDTO> atualizar(@PathVariable Long id, @Valid @RequestBody ProdutoFormDTO form) {
return ResponseEntity.ok(produtoService.atualizar(id, form));
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> deletar(@PathVariable Long id) {
produtoService.deletar(id);
return ResponseEntity.noContent().build();
}
}
3. PedidoController.java (Endpoints Protegidos de Checkout)
package br.com.tecloja.api.controller;
import br.com.tecloja.api.dto.PedidoDTO;
import br.com.tecloja.api.dto.PedidoFormDTO;
import br.com.tecloja.api.service.PedidoService;
import jakarta.validation.Valid;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/v1/pedidos")
public class PedidoController {
private final PedidoService pedidoService;
public PedidoController(PedidoService pedidoService) {
this.pedidoService = pedidoService;
}
@PostMapping
public ResponseEntity<PedidoDTO> realizarPedido(@Valid @RequestBody PedidoFormDTO form) {
return ResponseEntity.status(HttpStatus.CREATED).body(pedidoService.realizarPedido(form));
}
@GetMapping("/cliente/{clienteId}")
public ResponseEntity<List<PedidoDTO>> listarPorCliente(@PathVariable Long clienteId) {
return ResponseEntity.ok(pedidoService.listarPedidosPorCliente(clienteId));
}
}
4. CategoriaController.java (Endpoint Público de Filtros)
Endpoint público que retorna a lista de categorias disponíveis. O frontend Angular usa esse endpoint para popular os filtros de categorias no catálogo de produtos.
[!NOTE] Boas práticas de mercado — Por que não criamos um
CategoriaServiceaqui?Nos módulos anteriores, ensinamos o padrão de camadas Controller → Service → Repository para encapsular regras de negócio. Porém, criar um
CategoriaServiceneste caso seria over-engineering (complexidade desnecessária).A razão é simples: não existe regra de negócio alguma neste endpoint. Ele apenas lê uma tabela de domínio estática e retorna os dados formatados. Criar um Service que só delega ao Repository sem agregar lógica é chamado de “Pass-through Service” — um anti-padrão que cria burocracia sem valor real.
Esta prática é incentivada pelo princípio CQRS (Command/Query Responsibility Segregation): queries simples de leitura não precisam passar por uma camada de serviço.
Regra de ouro: Crie um
Servicequando houver lógica de negócio (comoPedidoServiceImplcom controle transacional de estoque). Acesse oRepositorydiretamente apenas para leituras simples sem lógica.
package br.com.tecloja.api.controller;
import br.com.tecloja.api.dto.CategoriaDTO;
import br.com.tecloja.api.repository.CategoriaRepository;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.stream.Collectors;
/**
* Endpoint público para listagem de categorias.
* Não requer autenticação JWT (liberado no SecurityConfig para GET /api/v1/categorias/**).
* Acessa o repository diretamente: query simples sem regra de negócio (padrão CQRS).
*/
@RestController
@RequestMapping("/api/v1/categorias")
public class CategoriaController {
private final CategoriaRepository categoriaRepository;
public CategoriaController(CategoriaRepository categoriaRepository) {
this.categoriaRepository = categoriaRepository;
}
@GetMapping
public ResponseEntity<List<CategoriaDTO>> listarTodas() {
List<CategoriaDTO> categorias = categoriaRepository.findAll().stream()
.map(c -> new CategoriaDTO(c.getId(), c.getNome()))
.collect(Collectors.toList());
return ResponseEntity.ok(categorias);
}
}
📬 5. Testes Práticos Interativos com Swagger UI
Em vez de depender de softwares externos (como Postman ou Insomnia), as APIs modernas do mercado utilizam a especificação OpenAPI e a interface gráfica do Swagger UI para documentar e permitir a execução de testes interativos dos endpoints diretamente pelo próprio navegador de internet!
Graças à dependência springdoc-openapi-starter-webmvc-ui que adicionamos ao pom.xml no Módulo 02, o Spring Boot gera e expõe essa documentação de forma totalmente automática e transparente.
🚀 Como Acessar o Swagger
- Inicie a API Spring Boot executando o backend.
- Abra o seu navegador e acesse a URL:
http://localhost:8080/swagger-ui/index.html
Você verá uma interface gráfica moderna contendo todos os Controllers da TecLoja catalogados: auth-controller, produto-controller, pedido-controller e categoria-controller.
🔑 Roteiro de Teste 1: Autenticando sua Sessão no Swagger
Como a maioria dos nossos endpoints está protegida pelo Spring Security e exige um Token JWT Bearer, precisamos obter o Token realizando o login e informá-lo ao Swagger:
- Na seção auth-controller, clique no endpoint
POST /api/v1/auth/login. - Clique no botão Try it out no canto superior direito do painel do endpoint.
- No campo Request body, substitua o JSON modelo pelas credenciais da Maria criadas pelo nosso
DataSeeder:{ "username": "usuario@email.com", "senha": "usuario123" } - Clique no botão azul Execute.
- Em Server response, você verá o status 200 OK e o corpo de resposta contendo o Token JWT gerado (um hash criptográfico longo iniciado por
eyJ...). Copie este Token completo (sem as aspas). - Role a página do Swagger até o topo e clique no botão Authorize (marcado com um ícone de cadeado) à direita.
- No campo Value do modal que abrir, digite o prefixo
Bearerseguido de um espaço e cole o token copiado (exemplo:Bearer eyJ...). - Clique em Authorize e feche o modal. A partir deste momento, todas as requisições feitas pelo Swagger carregarão o token de segurança nos cabeçalhos automaticamente!
📦 Roteiro de Teste 2: Consumindo os Endpoints
Agora que você está autenticado com o perfil de cliente da Maria, vamos testar os endpoints:
A. Listagem de Produtos
- Abra a seção produto-controller.
- Clique no endpoint
GET /api/v1/produtos. - Clique em Try it out e depois em Execute.
- Resposta Esperada: Status 200 OK contendo a lista com os produtos do
DataSeederno formato JSON.
B. Finalização de Compra (Checkout)
- Abra a seção pedido-controller.
- Clique no endpoint
POST /api/v1/pedidos. - Clique em Try it out e configure o JSON de entrada com um item de produto existente (ex: ID
1e quantidade1):{ "clienteId": 1, "itens": [ { "produtoId": 1, "quantidade": 1 } ] } - Clique em Execute.
- Resposta Esperada: Status 201 Created contendo o recibo do pedido faturado, mostrando o valor total calculado e o status
"PAGO"(pagamento aprovado imediatamente pela implementação didática).
🤔 Por que 401 Unauthorized e não 403 Forbidden?
401 Unauthorized significa “você não se identificou” — o token está ausente ou inválido. 403 Forbidden significa “você se identificou mas não tem permissão” — token válido, mas o papel do usuário não autoriza aquela operação. Nosso GlobalExceptionHandler retorna 401 para credenciais inválidas no login (BadCredentialsException). O Spring Security retorna 403 automaticamente quando o JWT é válido mas o papel (ROLE_USER vs ROLE_ADMIN) não bate com a rota acessada.
🔍 Checkpoint Final do Backend
Com a API rodando (./mvnw spring-boot:run), execute esta sequência no Swagger (http://localhost:8080/swagger-ui/index.html):
1. Acesso não autenticado (deve falhar):
GET /api/v1/pedidossem token → 401 Unauthorized ✓
2. Login com usuário padrão:
POST /api/v1/auth/logincom{"username":"usuario@email.com","senha":"usuario123"}→ JSON comtoken✓
3. Acesso autenticado:
- Autorize com
Bearer <token>no Swagger GET /api/v1/produtos→ lista de 6 produtos ✓
4. Teste de autorização de papel:
- Tente
POST /api/v1/produtoscom token dousuario@email.com→ 403 Forbidden (só ADMIN pode criar) ✓
⚠️ Erros Comuns
| Sintoma | Causa | Solução |
|---|---|---|
401 mesmo com token correto |
Token expirado ou chave JWT diferente | Gere um novo token via login; confirme que tecloja.jwt.secret não mudou |
403 no Swagger UI |
CSRF ou frameOptions bloqueando | Confirme .csrf(csrf -> csrf.disable()) e .headers(h -> h.frameOptions(f -> f.sameOrigin())) no SecurityConfig |
| Swagger UI retorna 404 | Dependência springdoc-openapi ausente |
Confirme que o bloco springdoc-openapi-starter-webmvc-ui está no pom.xml |
CORS blocked no navegador |
Origem do Angular não está na lista permitida | Confirme http://localhost:4200 em setAllowedOriginPatterns no corsConfigurationSource() |
Login retorna 400 com "password" |
Campo errado no body JSON | Use "senha" (não "password") — o AuthController lê request.get("senha") |
🏁 Conclusão do Backend
Parabéns! O desenvolvimento do Backend do projeto TecLoja foi totalmente concluído. Construímos:
- Um banco de dados completo relacional (H2 / Postgres).
- Mapeamento ORM avançado com Hibernate e tratamento de relações N:M.
- Uma camada de negócios robusta que gerencia transações ativamente.
- Uma API RESTful totalmente documentada, protegida por autenticação stateless com tokens JWT e blindada contra invasões externas por meio de regras de CORS bem delineadas.
No Módulo 06, mudaremos completamente de contexto! Deixaremos o Java de lado e iniciaremos a construção do nosso cliente SPA utilizando o poderoso ecossistema do Angular 18+.
📬 Entrega da Atividade via Microsoft Teams
Como você acaba de finalizar toda a construção da API Java, este é o momento de fazer o envio do repositório para avaliação!
- Garanta que todo o código do
backendfoi enviado (pushed) para um repositório público no seu GitHub pessoal. - Abra o Microsoft Teams na aba de Tarefas/Assignments da disciplina.
- Envie o link do repositório seguindo estritamente o padrão de nomenclatura abaixo no campo de texto ou título da entrega:
Padrão de Nomenclatura:
[TECLOJA_01] - [BACKEND] - Seu Nome CompletoLink:https://github.com/seu-usuario/nome-do-repositorio-backend