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

Medium.com如何实现安全嵌入内容而免受XSS攻击?

如何在Django+Medium Editor中安全实现类似Medium的链接嵌入功能

首先得明确核心原则:绝对不能让用户直接提交或控制HTML代码,包括<iframe>和<script>。Medium的逻辑也是用户只给链接,服务端自己生成安全的嵌入代码——这是防XSS的关键。下面结合Django生态,一步步实现:

1. 先搞个可信嵌入源白名单

你得先定义哪些平台是允许嵌入的,比如YouTube、Gist、Vimeo这些。白名单要精确到域名和路径格式,比如:

  • YouTube只允许https://www.youtube.com/embed/[视频ID]这种结构的链接
  • Gist只允许https://gist.github.com/[用户名]/[GistID]

为什么要这么严格?因为如果只检查域名,攻击者可能用https://youtube.com.evil-domain.com/xxx这种钓鱼域名绕过,所以必须同时验证域名和路径格式。

2. 服务端做链接解析与验证(核心安全环节)

在Django里写一个专门处理嵌入链接的视图,流程是:

  • 接收用户粘贴的纯文本链接
  • 用urllib.parse.urlparse解析链接,提取域名、路径
  • 把域名标准化(比如去掉www.,统一转小写),检查是否在白名单里
  • 对不同平台做路径格式校验(用正则表达式)
  • 验证通过就生成安全的<iframe>代码,不通过就转成普通链接

举个代码例子:

from django.http import JsonResponse
from urllib.parse import urlparse
import re

# 定义可信嵌入源的配置:域名、路径正则、iframe模板
TRUSTED_EMBEDS = {
    "youtube.com": {
        "path_pattern": r"^/embed/[a-zA-Z0-9_-]+$",
        "iframe_template": '<iframe width="560" height="315" src="https://www.youtube.com/embed/{video_id}" sandbox="allow-same-origin allow-scripts allow-popups" frameborder="0" allowfullscreen></iframe>'
    },
    "gist.github.com": {
        "path_pattern": r"^/[a-zA-Z0-9_-]+/[a-zA-Z0-9]+$",
        "iframe_template": '<iframe src="https://gist.github.com{path}.js" sandbox="allow-same-origin allow-scripts" frameborder="0"></iframe>'
    }
}

def process_embed_link(request):
    if request.method != "POST":
        return JsonResponse({"error": "仅支持POST请求"}, status=400)
    
    raw_link = request.POST.get("link", "").strip()
    if not raw_link.startswith(("http://", "https://")):
        return JsonResponse({"html": f'<a href="{raw_link}" target="_blank">{raw_link}</a>'})
    
    parsed_url = urlparse(raw_link)
    domain = parsed_url.netloc.lower()
    # 处理带www的域名,比如www.youtube.com → youtube.com
    if domain.startswith("www."):
        domain = domain[4:]
    
    # 检查域名是否在白名单
    if domain not in TRUSTED_EMBEDS:
        return JsonResponse({"html": f'<a href="{raw_link}" target="_blank">{raw_link}</a>'})
    
    embed_config = TRUSTED_EMBEDS[domain]
    # 校验路径格式
    if not re.match(embed_config["path_pattern"], parsed_url.path):
        return JsonResponse({"html": f'<a href="{raw_link}" target="_blank">{raw_link}</a>'})
    
    # 生成安全的iframe
    iframe_html = ""
    if domain == "youtube.com":
        video_id = parsed_url.path.split("/")[-1]
        iframe_html = embed_config["iframe_template"].format(video_id=video_id)
    elif domain == "gist.github.com":
        iframe_html = embed_config["iframe_template"].format(path=parsed_url.path)
    
    return JsonResponse({"html": iframe_html})

3. 给iframe加上安全属性

生成iframe时一定要加sandbox属性,这是浏览器层面的安全限制,比如:

  • allow-same-origin:允许iframe访问自身的源(部分平台需要)
  • allow-scripts:允许iframe执行脚本(YouTube、Gist都需要)
  • allow-popups:允许iframe弹出窗口(比如YouTube的全屏按钮)
  • 不要加allow-top-navigation,防止iframe跳转或控制父页面

这样即使嵌入的内容有意外风险,也无法突破iframe的沙箱限制。

4. 集成Medium Editor的粘贴事件

Medium Editor默认会把粘贴的链接转成普通<a>标签,我们需要监听粘贴事件,拦截纯文本链接,发送到后端处理:

// 初始化Medium Editor
const editor = new MediumEditor('.editable', {
    placeholder: '开始写内容...',
    toolbar: { /* 你的工具栏配置 */ }
});

// 监听粘贴事件
editor.elements[0].addEventListener('paste', async (e) => {
    const clipboardData = e.clipboardData || window.clipboardData;
    const pastedText = clipboardData.getData('text').trim();
    
    // 简单判断是否是HTTP链接
    if (/^https?:\/\/[^\s]+$/.test(pastedText)) {
        e.preventDefault(); // 阻止默认粘贴行为
        
        try {
            // 发送到后端处理
            const response = await fetch('/process-embed/', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/x-www-form-urlencoded',
                    'X-CSRFToken': getCSRFToken() // 记得获取Django的CSRF Token
                },
                body: `link=${encodeURIComponent(pastedText)}`
            });
            
            const data = await response.json();
            // 把后端返回的安全HTML插入编辑器
            editor.pasteHTML(data.html);
        } catch (err) {
            // 出错时降级为普通链接
            editor.pasteHTML(`<a href="${pastedText}" target="_blank">${pastedText}</a>`);
        }
    }
});

// 获取Django的CSRF Token函数
function getCSRFToken() {
    let cookieValue = null;
    if (document.cookie && document.cookie !== '') {
        const cookies = document.cookie.split(';');
        for (const cookie of cookies) {
            const trimmedCookie = cookie.trim();
            if (trimmedCookie.startsWith('csrftoken=')) {
                cookieValue = decodeURIComponent(trimmedCookie.substring(10));
                break;
            }
        }
    }
    return cookieValue;
}

5. 额外安全加固

  • URL规范化:处理链接时可以用urllib.parse.urlunparse把链接转成标准格式,避免攻击者用特殊字符或参数绕过校验
  • 定期更新白名单:根据业务需求添加新的可信平台,不要随便开放未知域名
  • 渲染时的二次检查:即使后端生成了HTML,渲染页面时也要确保用Django的mark_safe标记(但前提是你已经严格验证了内容,不要滥用mark_safe)
  • 绝对禁止用户提交HTML:编辑器要设置为只允许纯文本或经过处理的内容,不要开启富文本直接提交HTML的功能

这样一套流程下来,就能像Medium一样安全实现链接嵌入,既方便用户,又彻底避免XSS风险。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 07:19:00