MarkupString을 사용하여 Blazor에서 원시 HTML 렌더링
얼마 전 저는 CMS에서 가져온 일부 HTML을 렌더링하는 데 필요한 구성 요소를 구축하고 있었습니다. 변수에 HTML 문자열이 있고 @myHtml와 같은 템플릿에 놓았습니다. 물론 Blazor는 모든 것을 이스케이프 처리하고 실제 태그를 페이지의 텍스트로 렌더링했습니다. 내가 원하는 것이 아닙니다.
문제
기본적으로 Blazor는 템플릿에서 렌더링하는 모든 문자열을 인코딩합니다. 따라서 다음과 같은 경우:
@code {
private string content = "<strong>Hello</strong> <em>world</em>";
}
그리고 당신은 이렇게 합니다:
<div>@content</div>
페이지에 Hello world 대신 리터럴 텍스트 <strong>Hello</strong> <em>world</em>가 표시됩니다. Blazor는 XSS 공격을 방지하기 위해 의도적으로 이 작업을 수행하며, 이는 올바른 기본 동작입니다.
해결책: MarkupString
실제로 원시 HTML을 렌더링해야 하는 경우 문자열을 MarkupString로 래핑합니다.
<div>@((MarkupString)content)</div>
그리고 그게 다야! 이제 Blazor는 HTML을 실제 마크업으로 렌더링합니다. 변수에 할당할 수도 있습니다.
@code {
private string rawHtml = "<strong>Hello</strong> <em>world</em>";
private MarkupString HtmlContent => (MarkupString)rawHtml;
}
<div>@HtmlContent</div>
실제 사례
API에서 블로그 콘텐츠를 가져오고 미리 보기 구성 요소에서 렌더링해야 했습니다. 콘텐츠에는 제목, 코드 블록, 링크, 이미지 등 모든 종류의 HTML이 포함되어 있습니다. 대략적인 모습은 다음과 같습니다.
@inject HttpClient Http
@if (article is not null)
{
<article>
<h1>@article.Title</h1>
<div class="content">
@((MarkupString)article.HtmlBody)
</div>
</article>
}
@code {
[Parameter]
public int ArticleId { get; set; }
private ArticleDto? article;
protected override async Task OnInitializedAsync()
{
article = await Http.GetFromJsonAsync<ArticleDto>($"api/articles/{ArticleId}");
}
}
완벽하게 작동합니다. API의 HTML은 실제 마크업으로 렌더링됩니다.
#신뢰할 수 없는 내용에 주의하세요
이는 중요합니다. MarkupString는 HTML을 삭제하지 않습니다. <script> 태그를 포함하여 사용자가 제공한 모든 것을 렌더링합니다. 따라서 콘텐츠가 사용자 입력이나 신뢰할 수 없는 소스에서 나온 경우 먼저 해당 콘텐츠를 삭제해야 합니다.
Blazor에는 내장된 HTML 새니타이저가 없지만 HtmlSanitizer와 같은 라이브러리를 사용할 수 있습니다.
using Ganss.Xss;
@code {
private HtmlSanitizer sanitizer = new();
private MarkupString SafeHtml(string html)
{
var clean = sanitizer.Sanitize(html);
return (MarkupString)clean;
}
}
<div>@SafeHtml(untrustedContent)</div>
이는 <script>, onclick 핸들러와 같은 위험한 요소와 사용자 제공 콘텐츠에서 렌더링하고 싶지 않은 기타 항목을 제거합니다.
언제 사용하는가
나는 다음 용도로 MarkupString를 사용합니다.
- HTML 서버측으로 변환된 CMS 콘텐츠 또는 마크다운
- 리치 텍스트 편집기 출력
- 이메일 템플릿 미리보기
- 신뢰할 수 있는 소스에서 사전 구축된 HTML
사용자 입력에서 나오는 모든 항목은 항상 먼저 삭제하세요. 후회하는 것보다 안전합니다.
게시물이 마음에 드셨기를 바랍니다! @emimontesdeoca로 소셜 미디어를 통해 언제든지 저에게 연락해주세요.