从 NodeJS 服务器获取 Google Maps API 密钥的 CORS 问题

CORS Issue Fetching Google Maps API Key from NodeJS Server

提问人:French At Home 提问时间:10/29/2023 最后编辑:French At Home 更新时间:10/29/2023 访问量:45

问:

我开发了一个托管在 GitHub Pages 上的 JavaScript Webpack 前端应用程序,它与部署在 Heroku 上的 NodeJS 服务器进行通信。

为了增强安全性,我将 Google Maps API 密钥从前端代码中移出,并将其存储在 Heroku 环境变量中。

密钥由前端通过 NodeJS 服务器上的 API 端点检索。

但是,我遇到了阻止前端访问 API 密钥的 CORS 问题。

  1. 我将我的 Google Maps API 密钥存储在 Heroku 上的环境变量中:

heroku config:set GOOGLE_MAPS_API_KEY=my_api_key

  1. 后端代码(NodeJS Express):
app.get('/api/google-maps-api-key', (req, res) => {
    const googleMapsApiKey = process.env.GOOGLE_MAPS_API_KEY;
    if (!googleMapsApiKey) {
        res
            .status(500)
            .json({ error: 'Google Maps API key not found on the server.' });
    } else {
        res.json({ googleMapsApiKey });
    }
});
  1. 前端代码

我的地点.js:

fetch('https://nodejs-share-my-place-06b87a0b20ab.herokuapp.com/api/google-maps-api-key')
    .then(response => response.json())
    .then(data => {
        const googleMapsApiKey = data.googleMapsApiKey;
        document.querySelector('script').src = `https://maps.googleapis.com/maps/api/js?key=${googleMapsApiKey}&callback=Function.prototype`;
    })
    .catch(error => {
        console.error('Failed to fetch Google Maps API key:', error);
    });

SharePlace.js:

fetch('https://nodejs-share-my-place-06b87a0b20ab.herokuapp.com/api/google-maps-api-key')
    .then(response => response.json())
    .then(data => {
        const googleMapsApiKey = data.googleMapsApiKey;
        document.querySelector('script').src = `https://maps.googleapis.com/maps/api/js?key=${googleMapsApiKey}&callback=Function.prototype`;
    })
    .catch(error => {
        console.error('Failed to fetch Google Maps API key:', error);
    });

位置.js:

export async function getAddressFromCoords(coords) {
    try {
        const response = await fetch(
            `https://nodejs-share-my-place-06b87a0b20ab.herokuapp.com/api/google-maps-api-key`
        );
        if (!response.ok) {
            throw an Error('Failed to fetch Google Maps API key from the server.');
        }
        const data = await response.json();
        const googleMapsApiKey = data.googleMapsApiKey;

        const geocodeResponse = await fetch(
            `https://maps.googleapis.com/maps/api/geocode/json?latlng=${coords.lat},${coords.lng}&key=${googleMapsApiKey}`
        );

        if (!geocodeResponse.ok) {
            throw new Error('Failed to fetch address. Please try again!');
        }

        const geocodeData = await geocodeResponse.json();
        if (geocodeData.error_message) {
            throw new Error(geocodeData.error_message);
        }

        const address = geocodeData.results[0].formatted_address;
        return address;
    } catch (error) {
        throw new Error('Failed to fetch address. Please try again!');
    }
}

export async function getCoordsFromAddress(address) {
    try {
        const response = await fetch(
            `https://nodejs-share-my-place-06b87a0b20ab.herokuapp.com/api/google-maps-api-key`
        );
        if (!response.ok) {
            throw new Error('Failed to fetch Google Maps API key from the server.');
        }
        const data = await response.json();
        const googleMapsApiKey = data.googleMapsApiKey;

        const urlAddress = encodeURI(address);
        const geocodeResponse = await fetch(
            `https://maps.googleapis.com/maps/api/geocode/json?address=${urlAddress}&key=${googleMapsApiKey}`
        );

        if (!geocodeResponse.ok) {
            throw new Error('Failed to fetch coordinates. Please try again!');
        }

        const geocodeData = await geocodeResponse.json();
        if (geocodeData.error_message) {
            throw new Error(geocodeData.error_message);
        }

        const coordinates = geocodeData.results[0].geometry.location;
        return coordinates;
    } catch (error) {
        throw new Error('Failed to fetch coordinates. Please try again!');
    }
}
  1. 错误消息:

CORS 策略已阻止从源“https://sofiane-abou-abderrahim.github.io”在“https://nodejs-share-my-place-06b87a0b20ab.herokuapp.com/api/google-maps-api-key”处提取:请求的资源上不存在“Access-Control-Allow-Origin”标头。如果不透明响应满足您的需求,请将请求的模式设置为“no-cors”,以在禁用 CORS 的情况下获取资源。

分享位置.js:10 GET https://nodejs-share-my-place-06b87a0b20ab.herokuapp.com/api/google-maps-api-key net::ERR_FAILED 200 (OK)

SharePlace.js:15 无法获取 Google Maps API 密钥:TypeError:无法在 eval 处获取 (SharePlace.js:10:1)

  1. 我的问题:

如何解决阻止我在 GitHub Pages 上的前端应用程序向 Heroku 上的 NodeJS 服务器发出请求以获取 Google Maps API 密钥的 CORS 问题?

我应该对服务器或前端代码进行哪些更改,以确保在不影响安全性的情况下成功检索 API 密钥?

编辑:

根据评论的要求,这是我对某些文件的完整代码,以便我提供有关我的问题和我尝试过的内容的更多详细信息。

app.js(服务器端):

const express = require('express');
const bodyParser = require('body-parser');

const locationRoutes = require('./routes/location');

const app = express();

app.use(bodyParser.json());

const allowedOrigin =
    process.env.ALLOWED_ORIGIN || 'https://sofiane-abou-abderrahim.github.io';

app.use((req, res, next) => {
    res.setHeader('Access-Control-Allow-Origin', allowedOrigin);
    res.setHeader('Access-Control-Allow-Methods', 'POST, GET, OPTIONS');
    res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
    next();
});

app.use(locationRoutes);

const port = process.env.PORT || 3000;
app.listen(port, () => {
    console.log(`Server is running on port ${port}`);
});

SharePlace.js(前端):

import { Modal } from './UI/Modal';
import { Map } from './UI/Map';
import { getCoordsFromAddress, getAddressFromCoords } from './Utility/Location';
import { key } from '../key';

document.querySelector(
  'script'
).src = `https://maps.googleapis.com/maps/api/js?key=${key}&callback=Function.prototype`;

class PlaceFinder {
  constructor() {
    const addressForm = document.querySelector('form');
    const locateUserBtn = document.getElementById('locate-btn');
    this.shareBtn = document.getElementById('share-btn');

    locateUserBtn.addEventListener('click', this.locateUserHandler.bind(this));
    this.shareBtn.addEventListener('click', this.sharePlaceHandler);
    addressForm.addEventListener('submit', this.findAddressHandler.bind(this));
  }

  sharePlaceHandler() {
    const sharedLinkInputElement = document.getElementById('share-link');
    if (!navigator.clipboard) {
      sharedLinkInputElement.select();
      return;
    }

    navigator.clipboard
      .writeText(sharedLinkInputElement.value)
      .then(() => {
        alert('Copied into clipboard!');
      })
      .catch(err => {
        console.log(err);
        sharedLinkInputElement.select();
      });
  }

  selectPlace(coordinates, address) {
    if (this.map) {
      this.map.render(coordinates);
    } else {
      this.map = new Map(coordinates);
    }
    fetch(
      'https://nodejs-share-my-place-06b87a0b20ab.herokuapp.com/add-location',
      {
        method: 'POST',
        body: JSON.stringify({
          address: address,
          lat: coordinates.lat,
          lng: coordinates.lng
        }),
        headers: {
          'Content-Type': 'application/json'
        }
      }
    )
      .then(response => {
        return response.json();
      })
      .then(data => {
        const locationId = data.locId;
        this.shareBtn.disabled = false;
        const sharedLinkInputElement = document.getElementById('share-link');

        const pathnameParts = window.location.pathname.split('/');
        const repositoryName = pathnameParts[1]; // Get the first part of the pathname (GitHub username or organization)

        sharedLinkInputElement.value = `${location.origin}/${repositoryName}/my-place?location=${locationId}`;
      });
  }

  locateUserHandler() {
    if (!navigator.geolocation) {
      alert(
        'Location feature is not available in your browser - please use a more modern browser or manually enter an address.'
      );
      return;
    }
    const modal = new Modal(
      'loading-modal-content',
      'Loading location - please wait!'
    );
    modal.show();
    navigator.geolocation.getCurrentPosition(
      async successResult => {
        const coordinates = {
          lat: successResult.coords.latitude + Math.random() * 50,
          lng: successResult.coords.longitude + Math.random() * 50
        };
        const address = await getAddressFromCoords(coordinates);
        modal.hide();
        this.selectPlace(coordinates, address);
      },
      error => {
        modal.hide();
        alert(
          'Could not locate you unfortunately. Please enter an address manually!'
        );
      }
    );
  }

  async findAddressHandler(event) {
    event.preventDefault();
    const address = event.target.querySelector('input').value;
    if (!address || address.trim().length === 0) {
      alert('Invalid address entered - please try again!');
      return;
    }
    const modal = new Modal(
      'loading-modal-content',
      'Loading location - please wait!'
    );
    modal.show();
    try {
      const coordinates = await getCoordsFromAddress(address);
      this.selectPlace(coordinates, address);
    } catch (err) {
      alert(err.message);
    }
    modal.hide();
  }
}

const placeFinder = new PlaceFinder();

MyPlace.js(前端):

import { Map } from './UI/Map';
import { key } from '../key';

document.querySelector(
  'script'
).src = `https://maps.googleapis.com/maps/api/js?key=${key}&callback=Function.prototype`;

class LoadedPlace {
  constructor(coordinates, address) {
    new Map(coordinates);
    const headerTitleEl = document.querySelector('header h1');
    headerTitleEl.textContent = address;
  }
}

const url = new URL(location.href);
const queryParams = url.searchParams;
// const coords = {
//   lat: parseFloat(queryParams.get('lat')),
//   lng: +queryParams.get('lng')
// };
// const address = queryParams.get('address');
const locId = queryParams.get('location');
fetch(
  'https://nodejs-share-my-place-06b87a0b20ab.herokuapp.com/location/' + locId
)
  .then(response => {
    if (response.status === 404) {
      throw new Error('Could not find location!');
    }
    if (response.status === 500) {
      throw new Error('Invalid id!');
    }
    return response.json();
  })
  .then(data => {
    new LoadedPlace(data.coordinates, data.address);
  })
  .catch(err => {
    alert(err.message);
  });

Location.js(前端):

import { key } from '../../key';

export async function getAddressFromCoords(coords) {
  const response = await fetch(
    `https://maps.googleapis.com/maps/api/geocode/json?latlng=${coords.lat},${coords.lng}&key=${key}`
  );
  if (!response.ok) {
    throw new Error('Failed to fetch address. Please try again!');
  }
  const data = await response.json();
  if (data.error_message) {
    throw new Error(data.error_message);
  }
  const address = data.results[0].formatted_address;
  return address;
}

export async function getCoordsFromAddress(address) {
  const urlAddress = encodeURI(address);
  const response = await fetch(
    `https://maps.googleapis.com/maps/api/geocode/json?address=${urlAddress}&key=${key}`
  );
  if (!response.ok) {
    throw new Error('Failed to fetch coordinates. Please try again!');
  }
  const data = await response.json();
  if (data.error_message) {
    throw new Error(data.error_message);
  }

  const coordinates = data.results[0].geometry.location;
  return coordinates;
}
JavaScript 节点.js 谷歌地图 heroku cors

评论

0赞 sid 10/29/2023
你也可以共享Place.js代码吗?
0赞 French At Home 10/29/2023
根据要求,我分享了我的代码(与代码相同)以及我的代码,我也使用了 Google Maps API 密钥。SharePlace.jsMyPlace.jsLocation.js
1赞 Jaromanda X 10/29/2023
看看你请求的站点与你的请求所在的站点不同......这意味着它是跨起源的。您需要从服务器发送 CORS 标头
1赞 Jaromanda X 10/29/2023
此外,您正在向客户端公开您的 Google API 密钥 - 因此,您所做的一切都是徒劳的 - 顺便说一句......“sharePlace.js”和“myPlace.js”有什么区别
0赞 French At Home 10/29/2023
我确实从我的服务器发送了 CORS 标头(正如您在我的问题的编辑部分中的文件中看到的那样)。我正在向客户端公开我的 Google Maps API 密钥,但我的目标是从存储在 Heroku 中的环境变量中从我的服务器获取它。和之间的区别在于它们与两个不同的 HTML 文件相关(我在问题的编辑部分添加了两个完整的文件)。所以,现在你已经具备了理解我的目标的所有要素,也许可以给我一个解决方案。app.jsGOOGLE_MAPS_API_KEYSharePlace.jsMyPlace.js

答: 暂无答案