Solucionado (ver solução)
Solucionado
(ver solução)
11
respostas

Erro de cabeçalho ausente

Estou desenvolvendo um projeto com base nos conhecimentos aprendidos neste curso e nesse projeto configurei uma classe de configurações de segurança, conforme ensinado no curso, e também criei uma classe de configuração de Cors conforme abaixo:

package com.minascafe.api.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class CorsConfig {

@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("http://localhost")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowCredentials(true)
.exposedHeaders("Access-Control-Allow-Origin");
}};}}

Uma de minhas classes controller é:

package com.minascafe.api.controllers;
import com.minascafe.api.entities.User;
import com.minascafe.api.infra.security.TokenService;
import com.minascafe.api.record.AuthenticationDTO;
import com.minascafe.api.record.LoginResponseDTO;
import com.minascafe.api.record.RegisterDTO;
import com.minascafe.api.repositories.UserRepository;
import com.minascafe.api.services.AuthorizationService;
import jakarta.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("auth")
public class AuthenticationController {

@Autowired
private AuthenticationManager authenticationManager;

@Autowired
private UserRepository repository;

@Autowired
private TokenService tokenService;

@Autowired
private AuthorizationService authorizationService;
@PostMapping("/login")
public ResponseEntity login(@RequestBody @Valid AuthenticationDTO data){
try {
var usernamePassword = new UsernamePasswordAuthenticationToken(data.login(), data.senha());
var auth = this.authenticationManager.authenticate(usernamePassword);
var token = tokenService.generateToken((User) auth.getPrincipal());

return ResponseEntity.ok(new LoginResponseDTO(token));
} catch (AuthenticationException e) {
return ResponseEntity.status(HttpStatus.FORBIDDEN).body("Erro no login :(");
}}

@PostMapping("/register")
@CrossOrigin
public ResponseEntity register(@RequestBody @Valid RegisterDTO data) {
if (this.repository.findByLogin(data.login()) != null)
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("Usuário já cadastrado!");
String encryptedPassword = new BCryptPasswordEncoder().encode(data.password());
User newUser = new User(data.login(), encryptedPassword, data.role());
this.repository.save(newUser);

return ResponseEntity.status(HttpStatus.OK).body("Usuário cadastrado com sucesso!");
}}.

Quando executo e utilizo o frontend chamando o endpoint 'localhost/auth/login', com método POST passando um valor de usuario e de senha retorna na tela: 'Usuário ou senha incorretos, tente novamente!' e no console do navegador retorna um erro de cabeçalho ausente:

A cross-origin resource sharing (CORS) request was blocked because of invalid or missing response headers of the request or the associated preflight request .
To fix this issue, ensure the response to the CORS request and/or the associated preflight request are not missing headers and use valid header values.
Note that if an opaque response is sufficient, the request's mode can be set to no-cors to fetch the resource with CORS disabled; that way CORS headers are not required but the response content is inaccessible (opaque).
1 request
Request	Status	Preflight Request (if problematic)	Header	Problem	Invalid Value (if available)
 login	blocked	
 login	Access-Control-Allow-Origin	Missing Header.

Mas esse cabeçalho 'Access-Control-Allow-Origin' já foi configurado em meu arquivo de Cors. Simplesmente não estou entendendo. Alguém pode me dar uma luz, por favor?

11 respostas

Consegui resolver o problema adicionando a classe de Cors as configurações:

.allowedHeaders("Content-Type", "X-Requested-With", "accept", "Origin", "Access-Control-Request-Method", 
                        "Access-Control-Request-Headers", "Authorization")
.exposedHeaders("Access-Control-Allow-Origin", "Access-Control-Allow-Credentials")

Portanto minha classe de configuração de Cors ficou assim:

package com.minascafe.api.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class CorsConfig {

    @Bean
    public WebMvcConfigurer corsConfigurer() {
        return new WebMvcConfigurer() {
            @Override
            public void addCorsMappings(CorsRegistry registry) {
                registry.addMapping("/**") //Aplica a configuração CORS a todos os endpoints do aplicativo
                        .allowedOrigins("http://localhost", "http://localhost:3000") //especifica as origens que têm permissão para fazer solicitações de origem cruzada
                        .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
                        .allowedHeaders("Content-Type", "X-Requested-With", "accept", "Origin", "Access-Control-Request-Method", 
                        "Access-Control-Request-Headers", "Authorization")
                        .exposedHeaders("Access-Control-Allow-Origin", "Access-Control-Allow-Credentials") // Expor o cabeçalho Access-Control-Allow-Origin
                        .allowCredentials(true) //(Cookies, cabeçalhos de autorização, etc.)
                        .maxAge(3600);
            }
        };
    }
}

A partir daí gerei o build do projeto por meio do comando "mvn clean package" e gerei também o build do FrontEnd React com o comando: "npm run build". Como estou utilizando uma hospedagem VPS com linux, movi os arquivos de build do Java para o diretório "/opt/minas_cafe/MinasCafe.jar" e os arquivos de build do React (FrontEnd) para "/var/www/comerciominascafe.com/public_html/ e ao acessar o meu domínio abre a página inicial (página de login), mas ao inserir usuário e senha e clicar no botão para logar retorna o seguinte erro no console do navegador:

Failed to load resource: the server responded with a status of 404 (Not Found)
manifest.json:1 
        
Failed to load resource: the server responded with a status of 404 (Not Found)
login.js:55 Erro ao fazer login:  o {message: 'Network Error', name: 'AxiosError', code: 'ERR_NETWORK', config: {…}, request: XMLHttpRequest, …}code: "ERR_NETWORK"config: {transitional: {…}, adapter: Array(2), transformRequest: Array(1), transformResponse: Array(1), timeout: 0, …}message: "Network Error"name: "AxiosError"request: XMLHttpRequest {onreadystatechange: null, readyState: 4, timeout: 0, withCredentials: false, upload: XMLHttpRequestUpload, …}stack: "AxiosError: Network Error\n    at f.onerror (http://www.comerciominascafe.com/static/js/2.6a017a4d.chunk.js:1:220634)\n    at oe.request (http://www.comerciominascafe.com/static/js/2.6a017a4d.chunk.js:1:225742)\n    at async onClick (http://www.comerciominascafe.com/static/js/main.e8d474f3.chunk.js:1:54799)"[[Prototype]]: Error
overrideMethod @ console.js:213
onClick @ login.js:55
Show 1 more frame
Show less
localhost:8080/auth/login:1 
        
Failed to load resource: net::ERR_CONNECTION_REFUSED

Insira aqui a descrição dessa imagem para ajudar na acessibilidade ) Me parece estar reportando um erro no arquivo "manifest.json" e, não sei se devido a isso, mas também um erro na chamada axios para o endpoint de login (conforme controller acima).

Alguém consegue me ajudar? Estou totalmente perdido e o meu diretor está me cobrando enfaticamente! :(

Desde já agradeço todo e qualquer auxílio!

Olá, Edson.

Tudo bem?

O problema com o arquivo manifest.json parece indicar um problema de configuração do frontend React em relação ao arquivo de manifesto. No entanto, o erro mais preocupante é o ERR_CONNECTION_REFUSED, que indica que a solicitação para localhost:8080/auth/login não pode ser concluída porque o servidor não está respondendo.

Aqui estão algumas etapas que você pode checar para tentar resolver esses problemas:

  1. Verificar se o servidor Spring Boot está em execução e acessível na porta 8080:

    • Execute o servidor Spring Boot e verifique se ele está em execução sem erros.
    • Certifique-se de que não há problemas de firewall ou configurações de rede que impeçam o acesso ao servidor na porta 8080.
  2. Certificar-se de que o endpoint /auth/login no backend esteja configurado corretamente para aceitar solicitações POST:

    • Verifique a implementação do endpoint /auth/login no backend Spring Boot.
    • Certifique-se de que ele esteja mapeado corretamente para o método POST.
    • Verifique se não há erros na lógica de processamento de login no backend.
  3. Atualizar a configuração CORS para permitir solicitações do domínio onde o frontend está hospedado, incluindo http://localhost:3000:

    • Adicione http://localhost:3000 à lista de origens permitidas na configuração CORS do backend Spring Boot.
    • Certifique-se de que as configurações CORS estejam sendo aplicadas corretamente a todos os endpoints relevantes.
  4. Verificar se não há problemas de conectividade entre o frontend e o backend:

    • Verifique se os servidores frontend e backend estão hospedados em locais acessíveis.
    • Certifique-se de que não há problemas de firewall ou configurações de rede que bloqueiem a comunicação entre os servidores.
  5. Depurar o código do frontend para garantir que a solicitação Axios esteja sendo feita corretamente e o URL do endpoint de login esteja configurado corretamente:

    • Verifique o código do frontend React onde a solicitação Axios para o endpoint de login é feita.
    • Certifique-se de que o URL do endpoint de login esteja configurado corretamente.
    • Use ferramentas de depuração do navegador para verificar se a solicitação está sendo enviada corretamente.
  6. Confirmar se o arquivo manifest.json necessário para o frontend React está presente e configurado corretamente:

    • Verifique se o arquivo manifest.json está presente no diretório do frontend React.
    • Certifique-se de que as configurações dentro do arquivo manifest.json estejam corretas, especialmente qualquer URL relevante.

Ao seguir estas etapas e resolver qualquer problema que você encontrar durante o processo, você deverá conseguir corrigir os problemas de comunicação entre o frontend e o backend do seu aplicativo.

Espero ter ajudado. Qualquer dúvida manda aqui. Bons estudos.

Olá Renan,

  1. O sevidor Spring Boot está ativo e acessível na porta 8080. Eu insiro no Insomnia o endereço IP do meu servidor remoto e insiro o endpoint de login com um usuário e senha válido e ele me retorna o token:

Retorno do endpoint do servidor no Insomnia:

  1. Portanto o endpoint /auth/login no BackEnd está configurado corretamente para aceitar solicitções POST.

  2. A configuração CORS está configurada para aceitar solicitações do domínio onde o frontend está hospedado, inclusive adicionei lá configurações de acesso para 'localhost', 'IP do servidor' e 'domínio (www...)' do servidor, conforme abaixo:

Configuração de CORS: 4) Tanto o backend quanto o frontend estão hospedados em um mesmo VPS e o firewal ainda está desabilitado até a conclusão dos testes. 5) É possível depurar uma aplicação que está em produção? Pois as aplicações em desenvolvimento funcionam corretamente. 6) Com relação ao erro do arquivo manifest.json, o build do frontend estava gerando um arquivo chamado como 'xxx-manifest.json' (não me lembro o nome correto! :) ), alterei para manifest.json e esse erro cessou. Mas agora ao invés de reclamar do manifest.json está reclamando de favicon.ico: Erro do console

Ele está reclamando de 'No 'Access-Control-Allow-Origin' header is present on the requested resource' mas eu adicionei esse cabeçalho na classe de CORS em: '.allowedHeaders'. Está errado desta forma?

solução!

Oi Edson!

Essa configuração do CORS é chata, porque precisa configurar para o Spring e também apra o Spring Security. No seu caso está configurado apenas para o Spring.

Faz o seguinte, apague a sua classe CorsConfig e faça a configuração diretamente na classe SecurityConfigurations. Aqui tem um exemplo de um projeto que trabalhei integrado com um frontend javascript:

@Configuration
@EnableWebSecurity
public class SecurityConfigurations {

    @Value("${app.frontend.url}")
    private String frontend;

    @Autowired
    private SecurityFilter securityFilter;

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        return http
                .csrf(csrf -> csrf.disable())
                .cors(Customizer.withDefaults())
                .sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                .authorizeHttpRequests(req -> {
                    req.requestMatchers("/admin/**").hasRole("ADMIN");
                    req.requestMatchers("/login", "/login/**").permitAll();
                    req.anyRequest().authenticated();
                })
                .addFilterBefore(securityFilter, UsernamePasswordAuthenticationFilter.class)
                .build();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception {
        return configuration.getAuthenticationManager();
    }

    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        var configs = new CorsConfiguration();
        configs.addAllowedHeader("*");
        configs.addAllowedMethod("*");
        configs.addAllowedOrigin(frontend);

        var url = new UrlBasedCorsConfigurationSource();
        url.registerCorsConfiguration("/**", configs);

        return url;
    }

}

E no application.properties defina uma propriedade para configurar a url do frontend:

app.frontend.url=${APP_FRONTEND_URL:http://localhost:4200}

No ambiente de produção você declara uma variável de ambiente chamada APP_FRONTEND_URL com a url real da aplicação frontend.

Olá Rodrigo!

No caso da propriedade que você sugeriu acima para inserir no application.properties (app.frontend.url=${APP_FRONTEND_URL:http://localhost:4200}), essa porta '4200', eu vou inserir a porta do meu frontend, (que no meu caso é React, seria a porta 3000) correto?

Isso mesmo. Nesse exemplo que mostrei era uma aplicação frotend com Angular e por isso está a porta 4200.

Rodrigo,

na função sugerida acima ( public CorsConfigurationSource corsConfigurationSource()...), em 'return url;' 'url' está sublinhada em vermelho e informando a seguinte mensagem: 'Type mismatch: cannot convert from UrlBasedCorsConfigurationSource to CorsConfigurationSource'. Posso definir a assinatura do método como sendo: 'public UrlBasedCorsConfigurationSource corsConfigurationSource()...' para que o retorno de 'url' seja compatível? Funcionaria dessa forma?

Confere se os imports estão corretos, pois tem dois pacotes para essas classes:

import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

No caso da configuração do application.properties sugerida acima (app.frontend.url=${APP_FRONTEND_URL:http://localhost:4200}), é possível inserir mais de um domínio para permitir o acesso do frontend?

Sim, você pode fazer assim, por exemplo:

app.frontend.url=${APP_FRONTEND_URL:http://localhost:4200,http://localhost:3000,http://localhost:5000}

Mas aí vai precisar alterar a lógica na classe SecurityConfigurations:

@Bean
public CorsConfigurationSource corsConfigurationSource() {
    var configs = new CorsConfiguration();
    configs.addAllowedHeader("*");
    configs.addAllowedMethod("*");
    
    // quebra em várias strings fazendo a separação por virgula:
    var urlsFrontend = frontend.split(",");
    
    for(var url : urlsFrontend) {
        configs.addAllowedOrigin(url);
    }

    var url = new UrlBasedCorsConfigurationSource();
    url.registerCorsConfiguration("/**", configs);

    return url;
}

Rodrigo e Renan,

tudo funcionando corretamente aqui, graças às suas orientações. Sou imensamente grato, vocês são feras viu!

Quer mergulhar em tecnologia e aprendizagem?

Receba a newsletter que o nosso CEO escreve pessoalmente, com insights do mercado de trabalho, ciência e desenvolvimento de software