Solucionado (ver solução)

Importante

Você está vendo a versão anterior da nova experiência da Alura que estamos preparando para você. Em breve, ela ganha uma identidade visual novinha totalmente pensada em potencializar seus estudos!

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!