在 React 中,直接渲染用户输入的 HTML 字符串时,React 默认会将所有内容视为纯文本进行转义,以防止 XSS(跨站脚本)攻击。
如果要渲染用户输入的 HTML 文本,需要使用 dangerouslySetInnerHTML 属性,这是 React 提供的专门用于插入原始 HTML 的方式。
function HtmlRenderer({ userInputHtml }) {
// 创建一个包含 __html 属性的对象
const content = { __html: userInputHtml };
// 使用 dangerouslySetInnerHTML 渲染
return <div dangerouslySetInnerHTML={content} />;
}
// 使用组件
<HtmlRenderer userInputHtml="<p>这是用户输入的<strong>HTML</strong>内容</p>" />
React 会将 dangerouslySetInnerHTML 接收的对象中的 __html 属性值当作原始 HTML 处理, 直接将其插入到 DOM 中,而不进行任何转义。
在 Vue 中处理用户输入的 HTML 文本与 React 类似,但提供了更直接的指令方式:
- Vue 默认会对模板中的文本进行转义(如将
<转为<),防止 XSS 攻击 - 使用
v-html时,Vue 会跳过转义过程,直接将内容作为 HTML 插入到 DOM 中
在默认情况下,无论是 React 还是 Vue,当你直接传递一个包含 HTML 标签的字符串时,框架都会将其视为纯文本进行处理, 即对 HTML 特殊字符(如 <、>、& 等)进行转义,最终在页面上显示的是字符串本身,而不是解析后的 HTML 元素。
举个例子,如果你传递字符串 <strong>Hello</strong>,默认情况下,页面会显示 <strong>Hello</strong> 这段文本(包括尖括号和标签名),而不会显示加粗的 "Hello"。
这种行为是框架的安全设计,目的是防止 XSS(跨站脚本)攻击。如果框架默认直接解析 HTML 字符串,攻击者可能会注入恶意脚本(如 <script>窃取信息</script>),从而获得用户数据或控制页面。
如果非要使用v-html或者dangerouslySetInnerHTML, 比如下面代码:
const testHtml = '<div style="color:red;" onclick="alert(\'123\')"><p>123</p></div>'
// 在P标签直接使用
<p dangerouslySetInnerHTML={{ __html: testHtml}} ></p>
此时,就会执行alert(\'123\')。
因此,需要用 DOMPurify 等工具净化 HTML,覆盖所有风险场景:
import DOMPurify from 'dompurify';
const testHtml = DOMPurify.sanitize(
'<div style="color:red;" onclick="alert(\'123\')"><p>123</p></div>'
)
但是,这么一段代码不会执行打印语句:
<p dangerouslySetInnerHTML={{ __html: '<div style="color:red;"><p>123</p><script>console.log("1111")</script></div>', }} ></p>
这是因为现代浏览器会默认阻止通过 动态插入 的 <script> 标签执行脚本,这是浏览器层面的安全防护措施。
即使没有使用净化库,浏览器也会做基础防护。