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

无凭证调用AWS Amplify Storage.get访问公共S3资源报byteLength错误

问题背景

需要实现功能:未完成凭证登录的访客用户,可直接检索S3存储桶公共路径下存储的图片资源。
当前异常:在fetchNotes()方法中调用Storage.get(note.image)时,控制台抛出如下错误:

TypeError: Cannot read properties of undefined (reading 'byteLength')

已完成验证项:

  • 持有合法登录凭证的用户调用Storage.put()和Storage.get()方法均可正常执行
  • 直接通过浏览器访问对应S3对象URL可正常读取公共路径资源,S3存储桶本身的公共读权限配置无误
  • 初步判断异常由Amplify针对存储桶公共读访问的相关配置问题导致
相关代码
import React, { useState, useEffect } from 'react';
import './App.css';
import { Authenticator, useAuthenticator  } from '@aws-amplify/ui-react';
import { listNotes } from './graphql/queries';
import { createNote as createNoteMutation, deleteNote as deleteNoteMutation } from './graphql/mutations';
import { API, Storage } from 'aws-amplify';

const initialFormState = { name: '', description: '' }

function App() {
  const [notes, setNotes] = useState([]);
  const [formData, setFormData] = useState(initialFormState);
  const { user, authStatus } = useAuthenticator((context) => [context.user, context.authStatus ]);

  useEffect(() => {
    fetchNotes();
  }, []);

  async function deleteNote({ id }) {
    const newNotesArray = notes.filter(note => note.id !== id);
    setNotes(newNotesArray);
    await API.graphql({
      query: deleteNoteMutation,
      variables: { input: { id } },
      authMode: "AMAZON_COGNITO_USER_POOLS"
    });
  }

  async function onChange(e) {
    console.log(e.target.files[0]);
    if (!e.target.files[0]) return
    const file = e.target.files[0];
    setFormData({ ...formData, image: file.name });
    await Storage.put(file.name, file);
    fetchNotes();
  }

  async function fetchNotes() {
    console.log("ftech");
    const apiData = await API.graphql({ 
      query: listNotes
     });
    const notesFromAPI = apiData.data.listNotes.items;
    await Promise.all(notesFromAPI.map(async note => {
      if (note.image) {
        const image = await Storage.get(note.image, {config: {level: 'public' }}).catch((err) => { console.error(err); });
        if (image) {
          note.image = image;
        }
      }
      return note;
    }))
    setNotes(apiData.data.listNotes.items);
    console.log(notes);
  }

  async function createNote() {
    if (!formData.name || !formData.description) return;
    await API.graphql({
      query: createNoteMutation,
      variables: { input: formData },
      authMode: "AMAZON_COGNITO_USER_POOLS"
    });
    if (formData.image) {
      const image = await Storage.get(formData.image);
      formData.image = image;
    }
    setNotes([...notes, formData]);
    setFormData(initialFormState);
  }

  return (
    <div className="App">
      <Authenticator>
        {({ signOut, user }) => (
          <div>

            <h1>My Notes App</h1>
            <form onSubmit={createNote}>
              <input
                type="text"
                onChange={e => setFormData({ ...formData, 'name': e.target.value })}
                placeholder="Note name"
                value={formData.name}
              />
              <input
                type="text"
                onChange={e => setFormData({ ...formData, 'description': e.target.value })}
                placeholder="Note description"
                value={formData.description}
              />
              <input
                type="file"
                onChange={onChange}
              />
              <button type="submit">Create Note</button>
            </form>
            <div>
                <h1>Hello {user.username}</h1>
              </div>
            <button onClick={signOut}>Sign out</button>
          </div>
        )}
      </Authenticator>
      <div style={{ marginBottom: 30 }}>
        {
          notes.map(note => (
            <div key={note.id || note.name}>
              <h2>{note.name}</h2>
              <p>{note.description}</p>
              <p>{note.owner}</p>
              <p>{note.createdAt}</p>
              {
                (authStatus === 'authenticated') && (user.username === note.owner) && <button onClick={() => deleteNote(note)}>Delete note</button>
              }
              {
                note.image && <img src={note.image} style={{ width: 400 }} />
              }
            </div>
          ))
        }
      </div>
    </div>

  );
}

export default App;
排查思路
  • 首先定位参数传参错误:当前Storage.get的公共级别参数写法不符合Amplify API规范,嵌套config的写法在未登录状态下会触发SDK的签名逻辑,尝试读取不存在的用户身份凭证字段,最终抛出byteLength读取异常。
  • 其次检查GraphQL接口的鉴权配置:listNotes查询默认未指定鉴权模式时,未登录用户会被API拦截,根本拿不到完整的note列表数据。
  • 最后校验Amplify存储配置的公共访问开关:未开启guest访问权限时,即使S3桶本身开了公共读,Amplify SDK也会默认给请求加签名逻辑,未登录状态下必然报错。
解决方案
  1. 修正Storage.get的参数写法,根据你使用的Amplify版本选择对应写法:
    • Amplify V5版本直接传级别参数,不需要嵌套config:
    const image = await Storage.get(note.image, { level: 'public' })
    
    • Amplify V6版本公共路径访问不需要传level,直接拼接公共路径前缀即可,未登录状态下会自动返回无签名的公开URL:
    const image = await Storage.get(`public/${note.image}`)
    
  2. 修改listNotes请求的鉴权模式,允许未登录用户访问:
    const apiData = await API.graphql({ 
      query: listNotes,
      authMode: 'AWS_IAM'
    });
    
  3. 更新Amplify存储配置,开启访客公共访问权限:
    运行命令amplify update storage,在配置引导中选择开启Guest用户的读权限,之后执行amplify push同步配置到云端即可。
  4. 补充优化:useEffect中调用fetchNotes时,需要把authStatus加入依赖数组,避免登录状态切换时图片URL不刷新的问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 20:48:19