解决 React 中 img 加载图片报 403 Forbidden 错误

6 min

问题描述

在 React 项目中,使用 <img> 标签加载某些网页上的图片时,可能会遇到 403 Forbidden 错误。奇怪的是,如果直接在浏览器地址栏中打开该图片的 URL,图片却能正常加载。

例如,在下面的 React 代码中,尝试加载 object (图片 URL) 时可能会失败:

import React from 'react'
import { Carousel } from 'some-ui-library' // 假设使用了某个轮播组件
import styles from './MyComponent.module.css'

class MyComponent extends React.Component {
  bannerAfterChange = (current) => {
    console.log(current)
  }

  render() {
    // 假设 this.props.homemodel.bannerlist 是一个包含图片 URL 的数组
    if (!this.props.homemodel || !this.props.homemodel.bannerlist) {
      return <div>Loading...</div>
    }

    return (
      <Carousel ref="banner" autoplay={false} dots={false} afterChange={this.bannerAfterChange.bind(this)}>
        {
          this.props.homemodel.bannerlist.map((object, index) => {
            return (
              <div className={styles.slide} key={index}>
                {/* 直接加载图片可能会报 403 */}
                <img className={styles.banner_image} src={object} alt={`Banner ${index + 1}`} />
              </div>
            )
          })
        }
      </Carousel>
    )
  }
}

export default MyComponent

原因分析

这个问题通常与 HTTP 的 Referrer Policy(来源策略)有关。

浏览器在发起请求时,通常会带上 Referer HTTP 头,告诉服务器请求是从哪个页面发起的。某些图片服务器为了防止图片被盗链,会检查 Referer 头。如果 Referer 指向一个不允许的来源(比如你的本地开发环境 localhost 或其他非授权域名),或者请求没有 Referer 头,服务器就可能返回 403 Forbidden 错误。

当你直接在浏览器地址栏打开图片 URL 时,没有来源页面,浏览器可能不发送 Referer 头,或者发送一个服务器允许的 Referer,因此图片能正常加载。

解决方案

核心思路是控制浏览器发送 Referer 头的行为。

方案一:在 <img> 标签上设置 referrerPolicy (推荐)

这是最推荐的方式,因为它作用范围精确,符合组件化开发的思想。

修改 <img> 标签,添加 referrerPolicy='no-referrer' 属性:

<img
  className={styles.banner_image}
  src={object}
  alt={`Banner ${index + 1}`}
  referrerPolicy="no-referrer"
/>

referrerPolicy='no-referrer' 告诉浏览器在请求这张图片时,不要发送 Referer 头。这样,服务器就不会因为 Referer 不符合要求而拒绝请求。

优点:

  • 精确控制: 只影响当前 <img> 标签,不会影响页面上的其他请求。
  • 组件封装: 逻辑封装在组件内部,符合组件化开发思想,易于维护。

缺点:

  • 重复设置: 如果页面上有多个需要此策略的图片,需要在每个 <img> 标签上单独设置。

方案二:在 HTML <head> 中设置全局 <meta> 标签

你也可以在项目的 public/index.html (或其他入口 HTML 文件) 的 <head> 部分添加一个 <meta> 标签,来设置全局的 Referrer Policy:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <meta name="theme-color" content="#000000" />
  <meta name="description" content="Web site created using create-react-app" />

  {/* 在这里添加 meta 标签 */}
  <meta name="referrer" content="no-referrer">

  <title>React App</title>
</head>
<body>
  <noscript>You need to enable JavaScript to run this app.</noscript>
  <div id="root"></div>
</body>
</html>

这种方式会影响该页面发起的所有请求,除非某个请求(例如通过 fetch<img>referrerPolicy 属性)单独覆盖了策略。

优点:

  • 全局生效: 一次设置,对整个页面的所有请求(默认)生效,省去对单个元素重复设置的麻烦。

缺点:

  • 影响范围广: 可能会影响到页面上其他不需要此策略的请求,需要谨慎评估全局影响。
  • 不够精确: 控制粒度较粗,不如方案一灵活。

注意事项

使用 no-referrer 策略会有一些潜在影响:

  • 分析工具: 如果你使用了依赖 Referer 头的分析工具,它们可能无法正确追踪流量来源
  • 第三方服务: 某些第三方服务可能依赖 Referer 来验证请求合法性
  • 特定功能: 少数网站功能可能依赖 Referer 信息

Referrer Policy 详解

referrerPolicy 属性或 <meta name="referrer">content 属性可以接受多个值,用于更精细地控制 Referer 头的发送策略:

  • no-referrer: 不发送 Referer 头。
  • no-referrer-when-downgrade (默认值): 当从 HTTPS 页面请求 HTTP 资源时不发送 Referer 头,其他情况发送完整的 URL。
  • origin: 只发送来源页面的源(协议、域名、端口),不包含路径和查询参数。
  • origin-when-cross-origin: 同源请求时发送完整的 URL,跨域请求时只发送源。
  • same-origin: 只对同源请求发送 Referer 头,跨域请求不发送。
  • strict-origin: 始终只发送源。但从 HTTPS 页面请求 HTTP 资源时不发送。
  • strict-origin-when-cross-origin: 同源请求发送完整 URL;跨域请求时,如果安全级别不变(HTTPS->HTTPS)则发送源,如果降级(HTTPS->HTTP)则不发送。
  • unsafe-url: 始终发送完整的 URL(包括路径和查询参数),即使是从 HTTPS 页面请求 HTTP 资源。(不推荐,可能泄露敏感信息)

对于解决图片 403 问题,no-referrer 通常是最简单直接有效的策略。