Jak rozwiązać problem z CORS, SpringBoot + React

Rozpoczynając pracę nad aplikacją z wydzielonym frontendem np. w React i backendem np. w SpringBoot prawdopodobnie napotkasz problem próbując te części ze sobą połączyć.

Jeśli będziesz próbował pobrać dane z backandu i nie zakończy się to sukcesem a w konsoli zobaczysz następujący komunikat:

Zablokowano żądanie do zasobu innego pochodzenia: zasady „Same Origin Policy” nie pozwalają wczytywać zdalnych zasobów z „http://localhost:8080/” (brakujący nagłówek CORS „Access-Control-Allow-Origin”). Kod stanu: 404.

Powód jest prosty, twoja aplikacja SpringBoot działa na innym porcie niż twój serwer developerski hostujący frontend w Reakcie. A zgodnie z zabezpieczeniem CORS strona w przeglądarce może pobierać dane tylko z tej domeny na której się sama znajduje.

Zabezpieczenie to jest jak najbardziej potrzebne i w docelowym (produkcyjnym) systemie trzeba zadbać o spełnienie jego wymagań, natomiast podczas prac developerskich trochę nam utrudnia życie.

Teraz pokażę Ci 2 sposoby jak sobie z tym poradzić. Załóżmy że na frontendzie mamy funkcję która chce pobrać dane z backendu:

const getData = async ()=>{
    const response = await fetch("http://localhost:8080/hello");
    const data = await response.text();
    console.log("data", data);
  }

Po stronie backendu obsługuje ją kontroler z odpowiednią metodą:

@RestController
public class MainController {
	@GetMapping(value="hello")
	private String helloData() {
		return "Hi full-stack";
	}
}

I teraz metoda numer 1. Wystarczy że dodamy do kontrolera adnotację CrossOrigin, po tym powinien wyglądać następująco.

@CrossOrigin(origins = "*", allowedHeaders = "*")
@RestController
public class MainController {
	
	@GetMapping(value="hello")
	private String helloData() {
		return "Hi full-stack";
	}
}

I już będzie działać, natomiast jest to bardzo liberalna polityka i pozwala zupełnie na wszystko co oczywiście nie ma prawa pozostać w finalnej (produkcyjnej) wersji aplikacji.

Oczywiście można to modyfikować dla konkretnych domen itp. lub usuwać przed wdrożeniem ale dodaje to dodatkowej pracy i łatwo o czymś zapomnieć. Tu wkracza sposób numer 2.

Możemy pozostawić backend bez zmian natomiast dodać proxy do konfiguracji naszego frontendu, modyfikujemy tylko plik package.json a co najlepsze nie trzeba tego zmieniać przed wdrożeniem. Fragment z dodanym proxy będzie wyglądał tak:

... 
"scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "proxy": "http://localhost:8080",
  "eslintConfig": {
...

Od teraz w naszej aplikacji frontendowej powinniśmy odwoływać się do zasobów przez ścieżki względne, co będzie świetnie działało gdy finalnie dodamy nasz frontend do SpringBoota.

 const getData = async ()=>{
    const response = await fetch("/hello");
    const data = await response.text();
    console.log("data", data);
  }

Jeszcze mała wskazówka – po modyfikacji pliku package.json zrestartuj frontend.

Ja oczywiście preferuję drugi sposób ale wszystko zależy od konkretnej sytuacji więc zdecyduj sam.