웹 애플리케이션을 만들 때 서버에서 가져온 데이터를 화면(HTML)에 보여주는 것은 가장 기본적인 기능이다. 예를 들어 로그인한 사용자의 이름, 상품명, 게시글 제목 같은 것들이 그렇다.
스프링에서 뷰 템플릿 엔진으로 타임리프(Thymeleaf) 를 사용하면, 컨트롤러에서 넘겨준 데이터를 HTML에 아주 쉽게 출력할 수 있다. 이번 글에서는 타임리프의 가장 기본 기능인 텍스트 출력을 어떻게 하는지, 그리고 여기서 발생하는 이스케이프(escape) 개념과 th:text, th:utext의 차이를 정리해본다.
1. 컨트롤러에서 데이터 담기
스프링 컨트롤러는 Model 객체를 통해 데이터를 뷰에 전달한다.
@Controller
@RequestMapping("/basic")
public class BasicController {
@GetMapping("/text-basic")
public String textBasic(Model model) {
// "data"라는 이름으로 문자열 데이터를 담는다
model.addAttribute("data", "Hello Spring!");
// templates/basic/text-basic.html 로 이동
return "basic/text-basic";
}
}
여기서 "data"라는 키에 "Hello Spring!" 값이 담겨 뷰 템플릿으로 넘어간다.
2. 뷰 템플릿에서 출력하기
넘겨받은 데이터를 HTML에 출력하려면 타임리프 문법을 사용한다.
resources/templates/basic/text-basic.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>text-basic</title>
</head>
<body>
<h1>데이터 출력하기</h1>
<ul>
<!-- 속성 방식 -->
<li>th:text 사용 → <span th:text="${data}"></span></li>
<!-- 인라인 방식 -->
<li>인라인 출력 → [[${data}]]</li>
</ul>
</body>
</html>
- th:text : HTML 속성으로 데이터를 넣는 방식
- [[...]] : 콘텐츠 영역에 바로 데이터를 출력하는 방식
실행 결과: 브라우저 화면에 Hello Spring! 이 정상적으로 보인다.
3. 그런데 문제가 생긴다 – 이스케이프
데이터가 단순 텍스트라면 문제가 없지만, 만약 데이터 안에 HTML 태그가 들어 있다면 어떻게 될까?
@GetMapping("/text-unescaped")
public String textUnescaped(Model model) {
model.addAttribute("data", "Hello <b>Spring!</b>");
return "basic/text-unescaped";
}
개발자는 <b> 태그가 적용돼 "Spring!" 글자가 굵게 보이기를 기대한다.
하지만 타임리프는 기본적으로 이스케이프 처리를 한다.
- 화면: Hello <b>Spring!</b> (굵게 적용되지 않음)
- 페이지 소스: Hello <b>Spring!</b>
즉, < 와 > 같은 특수 문자를 HTML 엔티티로 변환해 태그가 실행되지 않고 글자로 그대로 표시된다.
왜 이렇게 동작할까?
(1). 보안을 위해서
브라우저는 <script> ... </script> 같은 태그를 만나면 자바스크립트 코드를 실행한다.
만약 누군가 입력칸에 이런 코드를 써서 서버로 보낸 뒤, 그게 그대로 실행된다면?
화면에 해킹 코드가 실행될 수 있다. (예: 알림창 뜨기, 쿠키 빼가기 등)
그래서 타임리프는 < 같은 기호를 그냥 “문자”로 바꿔서 보여준다.
즉, <script>를 “<script>” 라는 글자로 보여주지, 실제로 실행되지 않게 막아준다.
(2). 화면이 깨지지 않도록
HTML은 <태그>로 구조를 짠다.
만약 데이터 안에 <div> 같은 게 들어가 있으면, 원래 작성한 HTML 구조가 망가질 수도 있다.
예를 들어, 원래는 <ul><li>안녕</li></ul> 인데, 데이터가 <li>악의적인 태그</li> 라면?
리스트 구조가 꼬이면서 의도치 않은 화면이 나올 수 있다.
타임리프는 이런 문제를 막으려고, < 와 > 같은 기호를 “특수 문자(HTML 엔티티)”로 변환한다.
그래서 브라우저는 태그로 해석하지 않고, 그냥 글자로만 보여준다.
4. 해결 방법 – th:utext
HTML 태그까지 실행해서 보여주고 싶을 때는 th:utext 를 사용한다.
resources/templates/basic/text-unescaped.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>text-unescaped</title>
</head>
<body>
<h1>text vs utext</h1>
<ul>
<li>th:text → <span th:text="${data}"></span></li>
<li>th:utext → <span th:utext="${data}"></span></li>
</ul>
<h1><span th:inline="none">[[...]] vs [(...)]</span></h1>
<ul>
<li>[[...]] = [[${data}]]</li>
<li>[(...)] = [(${data})]</li>
</ul>
</body>
</html>
- th:text, [[...]] : 이스케이프 처리됨 (안전)
- th:utext, [(...)] : 이스케이프 해제, HTML 태그 적용됨
실행하면 Spring! 이 실제로 굵게 표시된다.
하지만 여기서 주의해야 한다.
왜냐하면 th:utext는 데이터를 있는 그대로 HTML로 실행하기 때문이다.
- 만약 데이터 안에 <script>...</script> 같은 코드가 있으면 그대로 실행돼서 해킹 문제가 생길 수 있다.
- 또, <div>나 <table> 같은 태그가 들어가 있으면 원래 화면 구조가 망가질 수 있다.
그래서 기본은 무조건 th:text 를 쓰는 게 안전하다.
단순히 글자를 굵게 보이고 싶으면, 데이터 자체에 <b>를 넣는 대신 이렇게 하면 된다.
<strong th:text="${data}"></strong>
이렇게 하면 데이터는 안전하게 문자 그대로 출력되면서, <strong> 태그 덕분에 굵게 표시된다.
마무리하며
오늘은 백엔드에서 데이터를 준비하고, 타임리프를 통해 화면에 보여주는 방법을 처음부터 차근차근 알아보았다.
가장 먼저 컨트롤러에서 Model 객체에 데이터를 담아 뷰로 넘겨주었다. 그리고 뷰 템플릿에서는 th:text와 [[...]]를 사용해 이 데이터를 HTML에 출력했다. 이렇게 하면 서버의 값이 화면에 안전하게 표시된다.
그런데 데이터 안에 HTML 태그가 들어 있는 경우에는 문제가 생겼다. <b> 태그처럼 글자를 굵게 만드는 태그도, 타임리프가 기본적으로는 그대로 글자로만 보여주기 때문이다. 이 과정을 이스케이프(escape) 라고 부른다.
이렇게 동작하는 이유는 두 가지였다.
- 첫째, 해킹 같은 보안 문제를 막기 위해서다. <script> 코드가 그대로 실행되면 큰 위험이 생길 수 있다.
- 둘째, HTML 구조가 깨지는 것을 막기 위해서다. 데이터 속 태그가 의도치 않게 HTML 구조에 섞이면 화면이 엉망이 될 수 있다.
이 문제를 해결하려면 th:utext나 [(...)]를 쓰면 된다. 이 방법은 데이터 안에 있는 태그를 실제로 실행시켜 화면에 적용한다. 그래서 <b> 태그가 있으면 글자가 굵게 표시된다. 하지만 이 방식은 조심해서 써야 한다. 사용자 입력 같은 신뢰할 수 없는 데이터에는 절대 쓰면 안 되고, 반드시 안전한 경우에만 사용해야 한다.
결국 정리하면 이렇게 생각하면 쉽다:
- 기본 출력은 무조건 th:text, [[...]] 로 한다.
- 글자를 꾸미고 싶으면, 데이터 안에 태그를 넣는 대신 <strong>이나 <span> 태그, CSS 스타일을 사용한다.
감사합니다.
'타임리프' 카테고리의 다른 글
| [Thymeleaf] 리터럴 (1) | 2025.09.15 |
|---|---|
| [Thymeleaf] URL링크 (0) | 2025.09.15 |
| [Thymeleaf] 유틸리티 객체와 날짜 (0) | 2025.09.15 |
| [Thymeleaf] 변수 -SpringEL (0) | 2025.09.11 |
| 타임리프(Thymeleaf)에 대하여 (0) | 2025.09.11 |
