스프링부트 XSS 방어, CSP로 완벽 무장하기

2026. 4. 22. 09:01·코딩 정보 공유



혹시 여러분의 웹 서비스가 사용자 입력 때문에 위험에 노출될 수 있다는 사실을 아시나요? 편리함 뒤에 숨은 XSS(Cross-Site Scripting) 공격은 사용자 세션 탈취, 악성 코드 주입 등 심각한 피해를 유발할 수 있습니다. 이 글에서는 스프링부트 기반 애플리케이션에서 XSS 공격을 효과적으로 방어하는 방법과, 강력한 보안 레이어인 CSP(Content Security Policy) 헤더를 완벽하게 적용하는 실전 노하우를 상세히 알려드립니다. 이 글을 통해 여러분의 서비스 보안 수준을 한 단계 끌어올릴 수 있습니다.




### XSS 공격의 위험성과 스프링부트의 기본 방어

XSS(Cross-Site Scripting)는 공격자가 악성 스크립트를 웹 페이지에 주입하여 다른 사용자의 브라우저에서 실행되도록 하는 웹 취약점입니다. 예를 들어, 게시판에 `

`와 같은 코드를 입력하면, 이 게시글을 보는 다른 사용자들은 해당 스크립트가 실행되어 쿠키 정보가 노출될 수 있습니다. 상상만 해도 아찔하죠? 이런 공격은 피싱, 악성 리다이렉션, 웹 페이지 변조 등으로 이어질 수 있어 반드시 방어해야 합니다.

스프링부트와 함께 주로 사용되는 템플릿 엔진들은 기본적인 XSS 방어 기능을 내장하고 있습니다. 예를 들어, Thymeleaf는 기본적으로 HTML을 렌더링할 때 특수 문자( `<`, `>`, `&`, `"`, `'`)를 HTML 엔티티( `<`, `>`, `&`, `"`, `'`)로 자동 변환(escaping)하여 스크립트가 실행되는 것을 막아줍니다.

```html
<!-


- Thymeleaf 예시 -->

 


```

위 코드에서 `userInput`에 `

`가 들어오더라도, Thymeleaf는 이를 `

<script>alert('XSS');</script>

`로 변환하여 단순 텍스트로 표시해줍니다. 하지만 개발자가 `th:utext`와 같이 이스케이핑을 비활성화하는 속성을 사용하거나, 직접 HTML을 구성하는 경우 취약점이 발생할 수 있습니다. 완벽한 방어를 위해서는 한층 더 강화된 보안 정책이 필요합니다.

### CSP 헤더: XSS 방어를 위한 강력한 최후의 방어선

CSP(Content Security Policy)는 웹 애플리케이션의 보안을 강화하는 HTTP 응답 헤더로, 브라우저가 특정 페이지에서 로드할 수 있는 리소스(스크립트, 스타일시트, 이미지 등)의 소스를 제한하는 역할을 합니다. 이는 XSS 공격이 성공하더라도 악성 스크립트가 실행되거나 외부로 데이터를 전송하는 것을 막아주는 '최후의 방어선' 역할을 합니다.

CSP는 다양한 지시문(directive)을 통해 세부적인 정책을 설정할 수 있습니다. 주요 지시문은 다음과 같습니다.

지시문 설명 예시
`default-src` 모든 리소스의 기본 로드 정책을 정의합니다. `default-src 'self'`
`script-src` 자바스크립트 소스를 제한합니다. `script-src 'self' https://trusted.cdn.com`
`style-src` CSS 스타일시트 소스를 제한합니다. `style-src 'self' 'unsafe-inline'`
`img-src` 이미지 소스를 제한합니다. `img-src 'self' data:`
`connect-src` XHR, WebSockets, EventSource의 연결 대상을 제한합니다. `connect-src 'self'`
`object-src` ``, ``, `` 태그의 소스를 제한합니다. `object-src 'none'`



### 스프링부트에서 CSP 헤더 완벽 적용하기

스프링 시큐리티를 사용하면 CSP 헤더를 매우 쉽게 적용할 수 있습니다. `SecurityFilterChain`을 통해 응답 헤더에 `Content-Security-Policy`를 추가하는 방식입니다.




1. **스프링 시큐리티 의존성 추가:**
`pom.xml` 또는 `build.gradle`에 스프링 시큐리티 의존성을 추가해주세요.

```xml
<!-


- Maven -->

org.springframework.boot
spring-boot-starter-security

```




2. **보안 설정 클래스 작성:**
`WebSecurityConfigurerAdapter`를 상속받거나, 스프링 시큐리티 5.7부터 권장되는 `SecurityFilterChain`을 사용하여 보안 설정을 구성합니다.

```java
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.header.HeaderWriter;
import org.springframework.security.web.header.writers.ContentSecurityPolicyHeaderWriter;

@Configuration
public class SecurityConfig {

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
// ... 다른 보안 설정 (인증, 권한 등) ...
.headers(headers -> headers
.contentSecurityPolicy(csp -> csp
.policyScriptSrc("script-src 'self' 'nonce-{nonce}'") // 인라인 스크립트 허용을 위해 nonce 사용 권장
.policyStyleSrc("style-src 'self' 'unsafe-inline'") // 개발 편의상 unsafe-inline을 사용했지만, 지양해야 합니다.
.policyImgSrc("img-src 'self' data:")
.policyDefaultSrc("default-src 'self'")
.policyObjectSrc("object-src 'none'")
.reportUri("/csp-report-endpoint") // CSP 위반 보고를 받을 엔드포인트
)
);
return http.build();
}
}
```

* `policyScriptSrc("script-src 'self' 'nonce-{nonce}'")`: `script-src`는 스크립트 로드 정책을 정의합니다. `'self'`는 동일 출처 스크립트만 허용하며, `'nonce-{nonce}'`는 서버에서 동적으로 생성된 `nonce` 값과 일치하는 인라인 스크립트만 허용합니다. 이는 인라인 스크립트 사용을 최소화하면서도 필요한 경우 안전하게 사용할 수 있도록 합니다.
* `policyStyleSrc("style-src 'self' 'unsafe-inline'")`: CSS 스타일 시트 정책입니다. `'unsafe-inline'`은 인라인 스타일을 허용하지만, 이는 잠재적인 XSS 벡터가 될 수 있으므로, 가능한 한 인라인 스타일을 사용하지 않고 외부 파일로 관리하거나 `nonce` 또는 해시 값을 사용하는 것이 좋습니다.
* `policyDefaultSrc("default-src 'self'")`: 다른 모든 리소스에 대한 기본 정책을 `'self'` (동일 출처)로 설정합니다.
* `policyObjectSrc("object-src 'none'")`: 플러그인(Flash, Java Applets) 사용을 전면 차단합니다. 이는 대부분의 최신 웹 애플리케이션에서 사용되지 않으며 보안상 위험하기 때문에 `'none'`으로 설정하는 것이 좋습니다.
* `reportUri("/csp-report-endpoint")`: CSP 위반이 발생했을 때, 브라우저가 해당 정보를 이 URL로 전송하도록 설정합니다. 이를 통해 실제 서비스에서 어떤 CSP 위반이 발생하는지 모니터링하고 정책을 개선할 수 있습니다.

**CSP 적용 시 주의사항:**

* **`'unsafe-inline'` 사용 최소화:** 인라인 스크립트나 스타일은 XSS 공격에 취약할 수 있으므로, 가능한 한 외부 파일로 분리하고 `nonce`나 해시 값을 사용하는 것을 권장합니다.
* **`nonce` 사용:** 동적으로 생성되는 `nonce` 값을 스크립트 태그와 CSP 헤더에 모두 적용하여 특정 인라인 스크립트만 실행되도록 하는 것이 가장 안전한 인라인 스크립트 허용 방법입니다.
* **보고 전용 모드 (`Content-Security-Policy-Report-Only`):** 처음 CSP를 적용할 때는 `Content-Security-Policy-Report-Only` 헤더를 사용하여 실제 정책을 적용하지 않고 위반 보고만 받으면서, 서비스에 미치는 영향을 최소화하고 정책을 다듬어 나가는 것이 현명합니다. 충분한 검증 후 `Content-Security-Policy` 헤더로 전환하세요.




스프링부트에서 XSS 공격 방어는 단순히 사용자 입력을 이스케이핑하는 것을 넘어, CSP 헤더와 같은 다층적인 보안 전략을 적용할 때 진정으로 강력해집니다. Thymeleaf와 같은 템플릿 엔진의 기본 방어와 CSP를 함께 활용한다면, 잠재적인 위협으로부터 여러분의 서비스를 더욱 안전하게 보호할 수 있습니다. 추가적인 팁으로, 정기적으로 보안 취약점 스캔 도구를 활용하여 애플리케이션의 잠재적 취약점을 점검하고, 최신 보안 패치를 항상 적용하는 습관을 들이는 것이 중요합니다.


저작자표시 (새창열림)

'코딩 정보 공유' 카테고리의 다른 글

Git 'Conflicted Index' 에러, 한 방에 해결하기!  (0) 2026.04.21
Gemini API 404 에러? 개발자 필독 해결법!  (0) 2026.04.20
LLM 토큰 절약: 캐싱으로 API 비용 잡는 법  (0) 2026.04.01
REST API 보안 핵심: JWT 완전 분석  (0) 2026.03.27
파이썬 자동 포스팅 봇: 스레드 API 토큰 활용법  (0) 2026.03.26
'코딩 정보 공유' 카테고리의 다른 글
  • Git 'Conflicted Index' 에러, 한 방에 해결하기!
  • Gemini API 404 에러? 개발자 필독 해결법!
  • LLM 토큰 절약: 캐싱으로 API 비용 잡는 법
  • REST API 보안 핵심: JWT 완전 분석
쿠키키키키
쿠키키키키
개발자의 이거저것입니다.
  • 쿠키키키키
    코딩 공부
    쿠키키키키
  • 전체
    오늘
    어제
    • 분류 전체보기 (360)
      • 웹1 (19)
      • 파이썬 (4)
      • MySQL (8)
      • 자바 (26)
      • 자바스크립트 (3)
      • 스프링 부트 프로젝트 연습 (17)
        • 스프링 부트 (3)
      • 자바 알고리즘 문제 (175)
      • 코딩 정보 공유 (33)
      • 정보처리기사 (39)
      • 코딩 영상 리뷰 (9)
      • 개인 프로젝트 (DNW) (20)
  • 블로그 메뉴

    • 링크
    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    JPA #JAVA
    자바
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.4
쿠키키키키
스프링부트 XSS 방어, CSP로 완벽 무장하기
상단으로

티스토리툴바