You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Next.js集成Google Photos Picker出现403错误,如何修复?

修复Google Photos选择器403错误(Next.js集成)

问题背景

在Next.js应用中集成Google Photos Library API,尝试实现类似Google Drive选择器的Google Photos选择器时,打开选择器出现403错误。当前代码可生成access token,且通过Axios调用API能正常获取数据,但选择器功能无法正常工作。

原代码

const GooglePhotosPicker = ({ onPick }) => {
  useEffect(() => {
    const loadGisClient = () => {
      window.onGoogleLibraryLoad = () => {
        const client = google.accounts.oauth2.initTokenClient({
          client_id: process.env.CLIENT_ID,
          scope: "https://www.googleapis.com/auth/photoslibrary.readonly",
          callback: (tokenResponse) => {
            if (tokenResponse.error) {
              console.error(
                "Error obtaining access token:",
                tokenResponse.error
              );
              return;
            }
            initializePicker(tokenResponse.access_token);
          },
        });

        document.getElementById("authorize_button").onclick = () => {
          client.requestAccessToken();
        };
      };

      const script = document.createElement("script");
      script.src = "https://apis.google.com/js/api.js";
      script.onload = () => {
        gapi.load("picker", { callback: onPickerApiLoad });
        window.onGoogleLibraryLoad();
      };
      document.body.appendChild(script);
    };

    loadGisClient();
  }, []);

  const onPickerApiLoad = () => {
    console.log("Picker API loaded");
  };

  const initializePicker = (accessToken) => {
    const picker = new google.picker.PickerBuilder()
      .addView(google.picker.ViewId.PHOTOS)
      .setOAuthToken(accessToken)
      .setDeveloperKey(process.env.KEY)
      .setCallback(pickerCallback)
      .build();
    picker.setVisible(true);
  };

  const pickerCallback = (data) => {
    if (data.action === google.picker.Action.PICKED) {
      const file = data.docs[0];
      onPick(file);
    }
  };

  return (
    <div>
      <button id="authorize_button">Pick a Photo from Google Photos</button>
    </div>
  );
};

export default function Page() {
  const handlePickedPhoto = (photo) => {
    console.log("Selected photo:", photo);
  };
  return (
    <div>
      <script src="https://accounts.google.com/gsi/client" async defer></script>
      <GooglePhotosPicker onPick={handlePickedPhoto} />
    </div>
  );
}

修复步骤与关键要点

1. 启用Google Picker API

Google Picker API与Google Photos Library API是独立服务,需在Google Cloud控制台单独启用:

  • 进入Cloud控制台的API库,搜索并启用Google Picker API

2. 修正API密钥配置

  • 确保process.env.KEY对应的API密钥,已在Cloud控制台的密钥设置中,将Google Picker API添加到允许调用的API列表
  • 检查密钥的应用限制(HTTP referrers),添加你的Next.js应用域名,本地开发需包含http://localhost:3000等测试地址

3. 调整Next.js环境变量前缀

客户端代码无法读取未加NEXT_PUBLIC_前缀的环境变量,需修改:

  • 将process.env.CLIENT_ID改为process.env.NEXT_PUBLIC_CLIENT_ID
  • 将process.env.KEY改为process.env.NEXT_PUBLIC_GOOGLE_API_KEY
  • 在.env.local文件中配置对应变量:
    NEXT_PUBLIC_CLIENT_ID=你的客户端ID
    NEXT_PUBLIC_GOOGLE_API_KEY=你的API密钥
    

4. 优化脚本加载与依赖等待

确保Picker API加载完成后再初始化选择器,避免依赖未就绪导致的错误,同时统一脚本加载逻辑:

修复后的代码

const GooglePhotosPicker = ({ onPick }) => {
  useEffect(() => {
    let pickerLoaded = false;

    // 加载Picker API
    const loadPickerApi = () => {
      gapi.load('picker', {
        callback: () => {
          pickerLoaded = true;
          console.log("Picker API loaded");
        }
      });
    };

    // 加载GIS客户端
    const loadGisClient = () => {
      window.onGoogleLibraryLoad = () => {
        const client = google.accounts.oauth2.initTokenClient({
          client_id: process.env.NEXT_PUBLIC_CLIENT_ID,
          scope: "https://www.googleapis.com/auth/photoslibrary.readonly",
          callback: (tokenResponse) => {
            if (tokenResponse.error) {
              console.error("Error obtaining access token:", tokenResponse.error);
              return;
            }
            // 等待Picker API加载完成后初始化
            if (pickerLoaded) {
              initializePicker(tokenResponse.access_token);
            } else {
              setTimeout(() => initializePicker(tokenResponse.access_token), 500);
            }
          },
        });

        document.getElementById("authorize_button").onclick = () => {
          client.requestAccessToken();
        };
      };

      const apiScript = document.createElement("script");
      apiScript.src = "https://apis.google.com/js/api.js";
      apiScript.onload = () => {
        loadPickerApi();
        window.onGoogleLibraryLoad();
      };
      document.body.appendChild(apiScript);
    };

    // 加载GSI认证脚本
    const loadGsiScript = () => {
      const gsiScript = document.createElement("script");
      gsiScript.src = "https://accounts.google.com/gsi/client";
      gsiScript.async = true;
      gsiScript.defer = true;
      document.body.appendChild(gsiScript);
    };

    loadGsiScript();
    loadGisClient();
  }, []);

  const initializePicker = (accessToken) => {
    const picker = new google.picker.PickerBuilder()
      .addView(google.picker.ViewId.PHOTOS)
      .setOAuthToken(accessToken)
      .setDeveloperKey(process.env.NEXT_PUBLIC_GOOGLE_API_KEY)
      .setCallback(pickerCallback)
      .build();
    picker.setVisible(true);
  };

  const pickerCallback = (data) => {
    if (data.action === google.picker.Action.PICKED) {
      const file = data.docs[0];
      onPick(file);
    }
  };

  return (
    <div>
      <button id="authorize_button">Pick a Photo from Google Photos</button>
    </div>
  );
};

export default function Page() {
  const handlePickedPhoto = (photo) => {
    console.log("Selected photo:", photo);
  };
  return (
    <div>
      <GooglePhotosPicker onPick={handlePickedPhoto} />
    </div>
  );
}
  • 若为外部用户测试,需将应用发布到「生产」状态,或添加测试用户到授权列表
  • 确保https://www.googleapis.com/auth/photoslibrary.readonly已添加到OAuth consent screen的已授权scopes中

内容的提问来源于stack exchange,提问作者Arnob10011

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 08:15:54