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

双服务器架构下如何在网页显示需认证的跨域图片?

解决跨域请求私有图片的可行方案

方法1:配置图片服务器的CORS规则

直接在图片服务器上调整CORS响应头,放开允许携带认证头部的跨域请求限制:

  • 设置Access-Control-Allow-Origin为主网页服务器的具体域名(不能用*,因为带自定义头部时*不被允许)
  • 添加Access-Control-Allow-Headers: Authorization,明确允许携带认证头
  • 若需同时携带Cookie,设置Access-Control-Allow-Credentials: true

以Express为例,用cors中间件配置的代码示例:

const cors = require('cors');

const corsOptions = {
  origin: 'https://your-main-domain.com', // 替换为主服务器域名
  allowedHeaders: ['Authorization'],
  credentials: true // 可选,仅当需要携带Cookie时启用
};

app.use(cors(corsOptions));

前端需通过fetch或XHR主动携带认证头,再将返回的图片Blob赋值给img标签(直接用img的src属性无法自定义请求头):

// fetch示例
fetch('https://image-server.com/private-photo.jpg', {
  headers: {
    'Authorization': 'Bearer YOUR_VALID_TOKEN'
  },
  credentials: 'include' // 对应服务器的credentials配置
})
.then(res => res.blob())
.then(blob => {
  const imgElement = document.getElementById('private-img');
  imgElement.src = URL.createObjectURL(blob);
});

方法2:主服务器作为代理层

让主服务器承担转发请求的角色,前端仅与主服务器交互,避免跨域问题:

  1. 前端请求主服务器的代理接口
  2. 主服务器在转发请求到图片服务器时,自动带上认证令牌(令牌可存储在主服务器的Session或Cookie中)
  3. 主服务器将图片流直接返回给前端

Express代理示例代码:

const axios = require('axios');

app.get('/proxy-image/:filename', async (req, res) => {
  try {
    // 从主服务器的会话或请求头中获取令牌
    const authToken = req.cookies.authToken || req.headers.authorization;
    const filename = req.params.filename;

    // 转发请求到图片服务器并携带认证头
    const imageResponse = await axios.get(`https://image-server.com/${filename}`, {
      headers: { Authorization: authToken },
      responseType: 'stream'
    });

    // 传递图片的Content-Type并返回流
    res.setHeader('Content-Type', imageResponse.headers['content-type']);
    imageResponse.data.pipe(res);
  } catch (error) {
    res.sendStatus(error.response?.status || 500);
  }
});

前端直接请求主服务器的代理接口即可:

<img src="/proxy-image/private-photo.jpg" alt="私有图片">

方法3:生成带签名的临时URL

让图片服务器生成限时有效的签名URL,前端无需携带认证头即可访问私有图片:

  1. 主服务器向图片服务器请求临时URL(携带认证令牌)
  2. 图片服务器验证令牌后,生成包含签名、过期时间的临时URL
  3. 前端使用该临时URL直接加载图片

图片服务器新增生成临时URL的接口:

const crypto = require('crypto');
const SECRET_SIGN_KEY = 'your-secure-secret-key'; // 需妥善保管

app.get('/generate-temp-url', async (req, res) => {
  const authHeader = req.headers.authorization;
  const targetFile = req.query.file;

  // 验证令牌和文件权限
  if (!private.includes(targetFile) || !authHeader || !somethingThatCheckAuthorization(authHeader)) {
    return res.sendStatus(400);
  }

  // 生成过期时间和签名
  const expires = Date.now() + 3600000; // 1小时后过期
  const signature = crypto.createHmac('sha256', SECRET_SIGN_KEY)
    .update(`${targetFile}:${expires}`)
    .digest('hex');

  const tempUrl = `https://image-server.com/${targetFile}?signature=${signature}&expires=${expires}`;
  res.json({ tempUrl });
});

修改图片服务器的原接口,支持验证临时URL:

app.get("/:file", async (req, res) => {
  const filename = req.params.file;
  const authHeader = req.headers.authorization;
  const { signature, expires } = req.query;

  // 优先处理临时URL请求
  if (signature && expires && Date.now() < parseInt(expires)) {
    const validSignature = crypto.createHmac('sha256', SECRET_SIGN_KEY)
      .update(`${filename}:${expires}`)
      .digest('hex') === signature;
    if (validSignature && private.includes(filename)) {
      return streamFile(res, `private/${filename}`);
    }
  }

  // 原有认证逻辑
  if (public.includes(filename)) {
    streamFile(res, `public/${filename}`);
  } else if (private.includes(filename) && authHeader) {
    const isValid = somethingThatCheckAuthorization(authHeader);
    isValid ? streamFile(res, `private/${filename}`) : res.sendStatus(400);
  } else {
    res.sendStatus(400);
  }
});

前端获取临时URL后加载图片:

fetch('/generate-temp-url?file=private-photo.jpg')
.then(res => res.json())
.then(data => {
  document.getElementById('private-img').src = data.tempUrl;
});

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.30 16:40:24