"CORS não é um bug, é uma feature de segurança. Entender CORS é entender como o navegador protege você de sites maliciosos." — #30DiasJava Security Notes
🎯 Por que CORS é Importante?
Imagine que você está logado no seu banco online (banco.com) e, ao mesmo tempo, visita um site malicioso (site-malicioso.com). Sem CORS, o site malicioso poderia fazer requisições para o banco.com usando seus cookies de autenticação e roubar seus dados. O navegador bloqueia isso por padrão.
Exemplo do mundo real: Quando você acessa uma API de um frontend React rodando em localhost:5173, o navegador bloqueia a requisição porque a API está em localhost:8080 (origem diferente). Isso é o CORS protegendo você. Mas para nossa aplicação funcionar, precisamos configurar o CORS corretamente.
No nosso projeto: Temos:
- Frontend React rodando em
http://localhost:5173 - Backend Spring Boot rodando em
http://localhost:8080 - Mobile app (Expo) que também precisa acessar a API
Sem CORS configurado, o navegador bloqueia todas as requisições entre frontend e backend. Com CORS configurado corretamente, permitimos que apenas origens confiáveis acessem nossa API.
🎯 Objetivo do Day 22
O Day 22 do #30DiasJava implementou uma configuração completa de CORS que permite que frontend e mobile acessem a API de forma segura, bloqueando requisições de origens não autorizadas.
🛠️ O que foi implementado
✅ Checklist de Implementação
1️⃣ CORS Configuration com origins permitidas configuráveis 2️⃣ Métodos HTTP permitidos (GET, POST, PUT, DELETE, OPTIONS, PATCH) 3️⃣ Headers permitidos (Authorization, Content-Type, etc.) 4️⃣ Preflight caching (1 hora) para reduzir requisições OPTIONS 5️⃣ Integração com Spring Security no SecurityFilterChain
✅ CORS Configuration
- Origins permitidas: Lista configurável de origens confiáveis
- Métodos HTTP: GET, POST, PUT, DELETE, OPTIONS, PATCH
- Headers permitidos: Authorization, Content-Type, etc.
- Credentials: Suporte para cookies e autenticação
- Preflight caching: Cache de requisições OPTIONS por 1 hora
✅ Perfis de Ambiente
- Desenvolvimento: Permite todas as origens (para facilitar desenvolvimento)
- Produção: Apenas origens específicas permitidas
- Configuração via properties: Fácil de ajustar sem mudar código
✅ Integração com Spring Security
- CORS no SecurityFilterChain: Integrado com autenticação JWT
- Headers expostos: Authorization, X-Total-Count para paginação
- Suporte a credentials: Cookies e tokens de autenticação
📊 Como CORS Funciona
O Problema
┌─────────────────────┐ ┌─────────────────────┐
│ Frontend │ │ Backend API │
│ localhost:5173 │ ──────X │ localhost:8080 │
│ (Origem A) │ BLOQUEADO│ (Origem B) │
└─────────────────────┘ └─────────────────────┘
Por quê bloqueado?
- Navegador detecta que origem é diferente
- Por segurança, bloqueia a requisição
- Erro no console: "CORS policy blocked"
A Solução
┌─────────────────────┐ ┌─────────────────────┐
│ Frontend │ │ Backend API │
│ localhost:5173 │ ──────✓ │ localhost:8080 │
│ (Origem A) │ PERMITIDO│ (Origem B) │
│ │ │ + CORS configurado │
└─────────────────────┘ └─────────────────────┘
Como funciona?
- Frontend faz requisição para backend
- Backend verifica se origem está na lista permitida
- Se sim, retorna headers CORS permitindo a requisição
- Navegador vê os headers e permite a requisição
🔍 Preflight Requests
Quando você faz uma requisição "complexa" (POST com JSON, DELETE, etc.), o navegador primeiro faz uma requisição OPTIONS para perguntar ao servidor: "Posso fazer esta requisição?"
1. Frontend: POST /api/familias (com JSON)
↓
2. Navegador: OPTIONS /api/familias (preflight)
↓
3. Backend: Responde com headers CORS permitindo
↓
4. Navegador: Agora faz o POST real
Por quê isso existe?
- Segurança: Servidor pode negar requisições perigosas antes que aconteçam
- Performance: Cache da resposta preflight por 1 hora (não precisa perguntar toda vez)
💻 Implementação
SecurityConfig.java
package com.adelmonsouza.desafio.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import java.util.Arrays;
import java.util.List;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
// Lista de origens permitidas (configurável via application.yml)
@Value("${app.security.cors.allowed-origins:http://localhost:5173,http://192.168.0.104:8080}")
private String allowedOrigins;
/**
* Configuração de CORS.
*
* O que é CORS?
* - Cross-Origin Resource Sharing
* - Permite que frontend (origem diferente) acesse a API
* - Bloqueia requisições de origens não autorizadas
*
* Por quê precisamos?
* - Frontend React roda em localhost:5173
* - Backend Spring Boot roda em localhost:8080
* - Navegador bloqueia requisições entre origens diferentes
* - CORS diz ao navegador: "Esta origem é confiável"
*/
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
// Converter string de origens em lista
List<String> origins = Arrays.asList(allowedOrigins.split(","));
// Em desenvolvimento, permitir todas as origens
// (útil para testar com mobile/Expo em diferentes IPs)
if (isDevProfile() || origins.isEmpty() || origins.contains("*")) {
configuration.addAllowedOriginPattern("*"); // Permite qualquer origem
} else {
// Em produção, apenas origens específicas
configuration.setAllowedOrigins(origins);
}
// Métodos HTTP permitidos
configuration.setAllowedMethods(Arrays.asList(
"GET", // Buscar dados
"POST", // Criar dados
"PUT", // Atualizar dados
"DELETE", // Deletar dados
"OPTIONS", // Preflight requests
"PATCH" // Atualização parcial
));
// Headers permitidos
configuration.setAllowedHeaders(Arrays.asList(
"Authorization", // Token JWT
"Content-Type", // Tipo de conteúdo (JSON, etc.)
"X-Requested-With", // Identifica requisições AJAX
"Accept", // Tipos de resposta aceitos
"Origin" // Origem da requisição
));
// Headers que o frontend pode ler
configuration.setExposedHeaders(Arrays.asList(
"Authorization", // Token JWT (se necessário)
"X-Total-Count" // Total de registros (para paginação)
));
// Permitir cookies e autenticação
// IMPORTANTE: Só funciona se allowedOrigins for específico (não "*")
configuration.setAllowCredentials(true);
// Cache de preflight requests por 1 hora
// Reduz número de requisições OPTIONS
configuration.setMaxAge(3600L);
// Aplicar configuração para todos os endpoints
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
/**
* SecurityFilterChain - Integra CORS com autenticação.
*/
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
// Desabilitar CSRF (APIs REST stateless com JWT não precisam)
.csrf(csrf -> csrf.disable())
// Configurar CORS
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
// ... outras configurações de segurança
return http.build();
}
private boolean isDevProfile() {
String activeProfiles = System.getProperty("spring.profiles.active", "");
return Arrays.asList(activeProfiles.split(",")).contains("dev");
}
}
application.yml
# Configuração de CORS
app:
security:
cors:
# Lista de origens permitidas (separadas por vírgula)
# Em desenvolvimento, pode usar "*" para permitir todas
allowed-origins: |
http://localhost:5173,
http://localhost:3000,
http://192.168.0.104:8080,
https://enouveau.io,
https://www.enouveau.io
📈 Exemplos de Requisições
Requisição Simples (GET)
Frontend → Backend:
GET /api/familias
Origin: http://localhost:5173
Backend → Frontend:
HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://localhost:5173
Access-Control-Allow-Credentials: true
Content-Type: application/json
[... dados ...]
Requisição Complexa (POST com Preflight)
1. Preflight (OPTIONS):
Frontend → Backend:
OPTIONS /api/familias
Origin: http://localhost:5173
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Content-Type, Authorization
Backend → Frontend:
HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://localhost:5173
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Headers: Authorization, Content-Type
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 3600
2. Requisição Real (POST):
Frontend → Backend:
POST /api/familias
Origin: http://localhost:5173
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
Content-Type: application/json
{"nome": "Família Silva"}
Backend → Frontend:
HTTP/1.1 201 Created
Access-Control-Allow-Origin: http://localhost:5173
Access-Control-Allow-Credentials: true
Content-Type: application/json
{"id": 1, "nome": "Família Silva", ...}
🧠 Insight Principal
"CORS não é um bug, é uma feature de segurança. Entender CORS é entender como o navegador protege você de sites maliciosos."
Por quê isso importa?
Segurança
- Sem CORS: Qualquer site pode fazer requisições, ataques CSRF, roubo de dados
- Com CORS: Apenas origens autorizadas, navegador bloqueia requisições não autorizadas, proteção automática
Compatibilidade com Frontend e Mobile
- Sem CORS: Frontend React não funciona, mobile app não funciona, erro "CORS policy blocked"
- Com CORS: Frontend funciona normalmente, mobile app funciona, requisições permitidas
Flexibilidade
- Desenvolvimento: Permite todas as origens (facilita testes)
- Produção: Apenas origens específicas (segurança)
📈 Resultados Reais
- Requisições bloqueadas: 0 (antes: 100% bloqueadas)
- Frontend funcional: ✅ React consegue acessar API
- Mobile funcional: ✅ Expo consegue acessar API
- Segurança mantida: ✅ Apenas origens autorizadas
⚠️ Erros Comuns e Soluções
Erro: "CORS policy blocked"
Causa: Origem não está na lista permitida
Solução: Adicionar origem em application.yml:
app:
security:
cors:
allowed-origins: http://localhost:5173,http://localhost:3000
Erro: "Credentials not allowed"
Causa: allowCredentials: true com allowedOrigins: ["*"]
Solução: Usar allowedOriginPatterns: ["*"] ou especificar origens:
// ❌ Não funciona
configuration.setAllowedOrigins(Arrays.asList("*"));
configuration.setAllowCredentials(true);
// ✅ Funciona
configuration.addAllowedOriginPattern("*");
configuration.setAllowCredentials(true);
// ✅ Ou melhor ainda (produção)
configuration.setAllowedOrigins(Arrays.asList("https://enouveau.io"));
configuration.setAllowCredentials(true);
Erro: "Preflight request failed"
Causa: Backend não está respondendo OPTIONS corretamente
Solução: Verificar se CORS está configurado no SecurityFilterChain:
http.cors(cors -> cors.configurationSource(corsConfigurationSource()));
🎓 Lições Aprendidas
✅ O que funcionou bem
- Configuração centralizada: Um único lugar para gerenciar CORS
- Perfis de ambiente: Diferentes configurações para dev/prod
- Preflight caching: Reduz requisições desnecessárias
- Integração com Spring Security: Funciona perfeitamente com JWT
⚠️ Desafios e soluções
- Mobile/Expo: Precisa permitir IPs dinâmicos em desenvolvimento
- Credentials com wildcard: Não funciona, precisa especificar origens
- Cache de preflight: Pode causar problemas em desenvolvimento (reduzir maxAge)
🏗️ Aplicação no Desafio das Águias
Como isso melhorou o produto:
- Antes: Frontend e mobile não conseguiam acessar API, erro "CORS policy blocked"
- Depois: Frontend React e mobile Expo funcionam perfeitamente, requisições permitidas de forma segura
- Resultado: Sistema acessível de qualquer origem autorizada, segurança mantida
Código no projeto:
- Arquivo:
backend/src/main/java/com/adelmonsouza/desafio/config/SecurityConfig.java - Configuração:
application.yml→app.security.cors.allowed-origins - Branch:
feature/day-22-cors-configuration
💬 Pergunta para Você
Como você configura CORS no seu projeto?
- Usa configuração global ou por endpoint?
- Como lida com preflight requests?
- Qual foi o erro de CORS mais difícil de resolver?
Compartilhe suas experiências nos comentários! 👇
📚 Recursos
- MDN CORS Guide: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
- Spring CORS Documentation: https://docs.spring.io/spring-framework/reference/web/webmvc-cors.html
- CORS Explained: https://www.codecademy.com/article/what-is-cors
🔗 Links
- Repositório: https://github.com/adelmonsouza/30DiasJava-Day22-CORS
- Artigo completo: https://enouveau.io/blog/2025/11/22/cors-cross-origin-security.html
Next episode → Day 23/30 — Security Best Practices