티스토리 뷰
1. csrf()
1.1 정의
- CSRF(Cross-Site Request Forgery)
:: 인증된 사용자의 권한을 악용한 요청 위조 공격
:: 악의적인 웹사이트가 사용자를 속여, 사용자가 로그인한 상태에서 웹사이트에 의도하지 않은 요청을 보내는 공격
:: CSRF 방지를 위한 CSRF Token 통한 방어책 제공( POST, PUT, DELETE 등 상태 변경 요청에 대해 CSRF 토큰이 필요)
// CORS가 방지하지 못하는 FORM 및 IMG 태그를 통한 CSRF 공격의 예
// CSRF에 대해 CORS로 일부는 방지할 수 있지만 네이티브 앱과 FORM 통한 공격은 토큰으로만 방어가능
// 1) 이미지 태그를 이용한 GET 방식 CSRF 예시
<img src="http://bank.com/transfer?accountNumber=5678&amount=10000"/>
// 2) 폼 자동 제출을 이용한 POST 방식 CSRF 예시
<body onload="document.forms[0].submit()">
<form action="http://bank.com/transfer" method="POST">
<input type="hidden" name="accountNumber" value="5678"/>
<input type="hidden" name="amount" value="10000"/>
<input type="submit" value="Pictures@"/>
</form>
</body>
1.2 CSRF 방어 활성화 VS 비활성화 비교
:: WB or 쿠키 사용인지를 확인하여 판별
// 비활성화 설정
http.csrf(AbstractHttpConfigurer::disable);
- 비활성화 해도 되는 경우 (== stateless 경우)
:: 세션을 사용 X일 때 (대부분의 REST API나 JWT 기반 인증 시스템은 Stateless 기반이기 때문에)
:: 세션 + 쿠키 기반 인증일 때 필요하므로, 세션이 없다면 CSRF 위험X
1) REST API 기반 서비스
2) JWT 기반 인증 시스템 (Stateless 구조)
3) 서버에서 세션을 사용하지 않는 경우
// 활성화 시
http.csrf(csrf -> csrf
.csrfTokenRepository(new HttpSessionCsrfTokenRepository())
// .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
);
- 활성화 해야하는 경우
:: CSRF는 기본적으로 formLogin 기반 인증에서만 활성화
:: 브라우저(쿠키 사용) or 세션 사용을 주의해서 구분
1) BFF (Backend For Frontend) 구조에서 BFF가 브라우저 요청을 처리 시, BFF 자체에서 CSRF 방어가 되어야 함.
단순한 프록시만 제공한다면, 실제 백엔드 서버에서 CSRF 보호 설정을 해야 안전.
2) JSESSIONID나 JWT를 쿠키에 저장 시, SameSite, HttpOnly 같은 보안 속성 + CSRF 방어를 병행
- 정리
구분 | CSRF설정 필요 여부 | 이유 |
브라우저 기반 웹 앱 (폼, AJAX) | 필요 | 브라우저는 쿠키를 자동으로 전송하므로 CSRF에 취약 |
SPA + 쿠키 기반 인증 (React/Vue + Session/JWT) |
필요 | |
Mobile App or REST Client (Token in Header) |
불필요 | 브라우저가 아님 + 쿠키 자동 전송 없음 → CSRF 우려 없음 |
OAuth2 / JWT (Stateless) | 대부분 불필요 | 세션 없음, 토큰은 명시적 전송 (보통 헤더) |
- JWT 기반 인증일 때,
- 쿠키를 사용하여 저장 / 인증 시, CSRF 필요
- LocalStorage, SessionStorage 등 클라이언트 측 스토리지 저장 + Authorization 헤더로 전송 시, 불필요
1.3 CSRF 토큰 저장소(CsrfTokenRepository)
:: 토큰을 영속적으로 저장하기 위해 Spring Security는 이를 위한 2가지 기본 저장소를 제공
:: HttpSession 또는 Cookie
저장소 | 설명 | 저장 위치 | 특징 |
HttpSessionCsrfTokenRepository | 기본값 | HttpSession | 세션 기반, 서버 메모리에 저장 |
CookieCsrfTokenRepository | 프론트 연동에 유리 | XSRF-TOKEN 쿠키 | React/Vue 연동 시 주로 사용 |
1.4 기본 속성 이름
- 저장 / 수신 / 송신의 쿠키, 헤더 명칭이 각기 다름
항목 | 설명 | 기본 값 |
DEFAULT_CSRF_COOKIE_NAME | 브라우저 저장 쿠키명 | XSRF-TOKEN |
DEFAULT_CSRF_HEADER_NAME | 서버로 보낼 때 헤더명 | X-XSRF-TOKEN |
DEFAULT_CSRF_PARAMETER_NAME | FORM 혹은 META 태그를 통해 전달할때 키값 | _csrf |
DEFAULT_CSRF_TOKEN_ATTR_NAME | HttpSession 저장 시 키값 | .CSRF_TOKEN |
1.5 클라이언트 측 처리 방식
1. Synchronizer Token Pattern (STP)
:: 전통적인 서버 렌더링 방식
:: 서버가 HTML의 <form> 기반 요청은 <input type="hidden">으로 토큰 삽입 -> 서버는 폼의 _csrf 필드를 통해 토큰 값을 검증
<form action="/transfer" method="POST">
<input type="hidden" name="_csrf" value="${_csrf.token}"/>
<input type="hidden" name="accountNumber" value="5678"/>
<input type="hidden" name="amount" value="10000"/>
<input type="submit" value="Transfer"/>
</form>
2. Cookie-to-Header Token (SPA 방식)
:: SPA나 REST API와 같은 JSON 기반 요청에서는 자바스크립트를 통해 CSRF 토큰을 헤더로 전송해야 함
(META 태그 + JavaScript 기반 요청 (AJAX, Fetch))
:: 서버가 쿠키(XSRF-TOKEN)로 토큰 전달 → JS가 읽어 헤더에 넣어 전송
// META 태그 설정 - CSRF 토큰과 헤더명 전달
<meta name="_csrf_header" content="${_csrf.headerName}" />
<meta name="_csrf" content="${_csrf.token}" />
// jQuery AJAX 요청 예시
var header = $("meta[name='_csrf_header']").attr('content');
var token = $("meta[name='_csrf']").attr('content');
$.ajax({
url: "/api/data",
type: "POST",
beforeSend: function(xhr) {
xhr.setRequestHeader(header, token);
},
success: function(res) {
console.log(res);
}
});
2. cors()
2.1 정의
- CORS(Cross-Origin Resource Sharing)
:: 다른 도메인 사이에서 리소스 공유를 허용하거나 제한하는 HTTP 헤더 기반 매커니즘
:: 브라우저의 동일 출처 정책을 완화시켜, 한 도메인에서 다른 도메인의 리소스에 접근할 수 있도록 허용 - 브라우저의 동일 출처 정책(Same-Origin Policy, SOP)
:: 한 웹 페이지에서 로드된 스크립트가 다른 출처의 리소스에 접근하는 것을 제한
:: 웹 브라우저가 한 웹 페이지의 스크립트가 다른 웹 페이지의 데이터에 접근하는 것을 허용하지만,
두 웹 페이지의 출처가 동일한 경우에만 허용(스킴, 호스트, 포트가 모두 일치)
2.2 기본 설정
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
// 설정 내역을 명시적으로 등록해서 사용해야 함
http.cors((cors) -> cors.configurationSource(reactConfigurationSource));
return http.build();
}
}
@Configuration
public class CorsConfig {
@Bean
public CorsConfigurationSource reactConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
// 1) Origins: setAllowedOrigins
// - 허용할 오리진(origin) 목록 설정 (오리진: 프로토콜 + 호스트 + 포트)
configuration.setAllowedOrigins(Arrays.asList("http://localhost:5173"));
// 2) Methods: setAllowedMethods
// - 허용할 HTTP 메서드 지정 (CORS Pre-flight 요청 시 검사 대상)
configuration.setAllowedMethods(Arrays.asList("GET", "POST"));
// 3) Headers: setAllowedHeaders
// - 클라이언트가 요청 시 사용할 수 있는 헤더 설정
configuration.setAllowedHeaders(Arrays.asList("*"));
// 자격 증명(쿠키, Authorization 헤더 등)을 포함할지 여부 (필요 시 true)
// configuration.setAllowCredentials(true);
// CORS 설정을 URL 패턴에 등록
// UrlBasedCorsConfigurationSource 객체 생성하여 주입
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration); // 모든 요청에 대해 CORS 적용
return source;
}
}
2.3 Spring에서 CORS 설정 방법 비교 – 메서드 / 클래스 / 글로벌 단위
- CORS 설정을 3가지 수준에서 적용 가능
1) 개별 핸들러 메서드(@RequestMapping) 수준
2) Controller 클래스 수준
3) 글로벌 애플리케이션 수준
1. 메서드(RequestMapping) 단위 설정
:: 테스트 또는 특정 API만 공개할 때 유용
@CrossOrigin(
origins = "*",
maxAge = 1800 // Preflight 요청 캐시 1800초 (30분)
)
@GetMapping("/api/test")
public String testApi() {
return "Hello CORS";
}
2. 클래스(Controller) 단위 설정
:: 도메인별 API 접근 제어가 필요한 경우
:: 보안상 특정 Origin만 허용할 필요가 있을 때
@CrossOrigin(
origins = "http://localhost:5173", // 특정 Origin만 허용
maxAge = 3600 // Preflight 요청 캐시 1시간
)
@RestController
@RequestMapping("/api")
public class SampleController {
// ...
}
3. 글로벌 CORS 설정 (CorsConfigurationSource)
:: 공통 정책을 일괄 적용하려는 경우
http.cors(cors -> cors.configurationSource(corsConfigurationSource()));
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration config = new CorsConfiguration();
config.setAllowedOrigins(List.of("http://localhost:5173"));
config.setAllowedMethods(List.of("GET", "POST"));
config.setAllowedHeaders(List.of("*"));
config.setAllowCredentials(true); // 쿠키 포함 허용
config.setMaxAge(3600L);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
return source;
}
참고
ASAC 수업자료
https://docs.spring.io/spring-security/reference/servlet/exploits/csrf.html
Cross Site Request Forgery (CSRF) :: Spring Security
To handle an AccessDeniedException such as InvalidCsrfTokenException, you can configure Spring Security to handle these exceptions in any way you like. For example, you can configure a custom access denied page using the following configuration: Configure
docs.spring.io
'정리용 > Spring Security' 카테고리의 다른 글
[Security] 2-3. WebSecurityCustomizer (0) | 2025.06.12 |
---|---|
[Security] 2-2. SecurityFilterChain 옵션(sessionManagement ...) (0) | 2025.06.12 |
[Security] 2. Spring Security 설정 (인증 / 인가) (0) | 2025.06.12 |
[Security] 1-3. Spring Security - HttpSession (0) | 2025.06.10 |
[Security] 1-2. Spring Security 인증 절차 (0) | 2025.06.09 |
- Total
- Today
- Yesterday
- useMemo
- useState
- asac7#asac
- git
- ASAC
- useRef
- useContext
- memo
- react
- ssh
- acas#acas7기
- asac#asac7기
- useEffect
- useCallback
- Nginx
- acac
- useLayoutEffect
- asac7기
- useReducer
- asac7
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 |