java & spring

[Spring Security] csrf().disable()하는 이유는?! 그리고 CORS

만나쓰 2024. 11. 3. 21:58

0. Intro

매번 CSRF, CORS를 공부하고 뒤돌아서 잊어버렸는데 면접에서 대답 못하는 사태가 벌어졌다..

이전에 spring security에서 csrf().disable()에 대해서 제대로 이해하지 않고 사용한 것도 생각나서 이번 기회에 확실하게 정리하고 기억하고자 한다.

 

1. SOP와 CORS

브라우저는 기본적으로 같은 출처에서만(origin) 리소스를 공유할 수 있다라는 SOP(Same-origin policy) 정책을 따르고 있다. 하지만 다른 출처와의 상호작용이 필요한 경우를 위해 특정 조항에 해당하는 리소스 요청은 출처가 다르더라도 허용하기로 했다. 그 중 하나가 CORS(Cross-Origin Resource Sharing) 정책을 지킨 리소스 요청이다. 다시 말해, SOP의 불편함을 해소하면서 보안을 지키기 위해 등장한 것이 CORS이다. CORS를 통해 다른 출처의 리소스 공유에 대한 허용/비허용을 설정하는 것이다.

 

그리고 새롭게 알게 된 사실! CORS 정책 검사는 출처를 비교하는 로직이 서버에 구현된 스펙이 아니라, 브라우저에 구현되어 있는 스펙이라고 한다. 

서버는 정상적으로 응답하고, 브라우저가 응답을 분석해서 CORS 정책 위반이라고 판단되면 그 응답을 사용하지 않고 버린다. 즉, CORS는 브라우저의 구현 스펙에 포함되는 정책이기 때문에, 브라우저를 통하지 않고 서버 간 통신을 할 때는 이 정책이 적용되지 않는다. 그래서 Postman으로 API 요청할 때는 CORS 에러가 발생하지 않다가 뒤늦게 브라우저 테스트할 때 CORS 에러를 마주하게 됐던 것이었다.

 

전체적인 CORS 검증하는 흐름은 다음과 같다.
1. 웹 브라우저(클라이언트)가 HTTP Request Header에 'Origin' 필드에 출처를 담아서 보낸다.

2. 이후 서버는 HTTP Response Header의 'Access-Control-Allow-Origin' 필드에 접근을 허용한 출처 url을 포함하여 응답한다.

3. 그럼 웹 브라우저(클라이언트)는 Origin과 Access-Control-Allow-Origin을 비교하고, 유효하지 않은 경우 CORS Error로 판단하고 응답을 사용하지 않는다.

더 자세히는 simple request, preflight request, credential request가 있는데, 이 부분은 생략한다.

 

결국 CORS 해결책은 서버의 허용이 필요한 것이고, credentail request의 경우 Access-Control-Allow-Credential, Access-Control-Allow-Origin, Access-Control-Allow-Methods, Access-Control-Allow-Headers 항목에 대한 설정이 필요하다. 이때 와일드카드(*)이면 안 되니 주의하자.

CORS는 서버에서 검증하고 에러가 발생하는 것이 아니라, 웹 브라우저(클라이언트)의 스펙이라는 것을 명심하자!

 

2. CSRF란? 

Cross-Site Request Forgery, 사이트 간 요청 위조는 웹 사이트 취약점 공격 기법이다. 사용자가 자신의 의지와는 무관하게 공격자가 의도한 행위를 특정 웹사이트에 요청하게 하는 공격을 말한다. 

 

csrf가 성공하기 위한 세가지 조건은 다음과 같다.

1. 사용자는 보안이 취약한 서버로부터 이미 로그인이 되어있는 상태

2. 쿠키 기반의 서버 세션 정보를 획득할 수 있어야 함

3. 공격자는 서버를 공격하기 위한 요청 방법에 대해 미리 파악해야 함

 

CSRF 공격을 방지하려면, 공격자 요청과 서비스 웹사이트 상의 실제 요청을 구분해야 한다. 이 두 요청을 구분할 수 없다면 CSRF 공격에 취약해질 수 있다.

그래서 반대로 서버의 세션 기반 인증 방식이 아닌, 클라이언트 기반 인증 방식(토큰 등)을 사용하는 경우는 서버에 인증 정보를 보관하지 않고 각 요청마다 인증하게 되므로 비활성화해도 괜찮다.

대부분의 블로그에서 REST API, Stateless 어쩌고 하면서 토큰 기반 인증 방식은 CSRF가 발생하지 않는다고 하는데... 정확히는 토큰의 저장 위치가 핵심인 것 같다.

세션 기반 인증이냐, 토큰 기반 인증이냐의 차이가 아니라, 토큰이어도 그것을 쿠키에 저장하면  CSRF 공격이 가능하다.

그래서 보통 JWT 토큰을 Authorization 헤더에 담아서 요청을 보내고, 이럴 경우 CSRF 취약점에 대해서 어느정도 안전하다고 말할 수 있다.

 

3. 그래서 spring security에서 http.crsf().disable()하는 이유는?!

위에서 말했듯이,Rest API는 일반적으로 stateless 프로토콜인 HTTP를 기반으로 하고 있고, 세션이 아닌 JWT와 같은 토큰 기반 인증을 사용하는 경우가 많기 때문에 대부분 CSRF 보호가 불필요하다. 

추가적인 설명이 필요하다. REST API는 일반적으로 stateless한 토큰 기반 인증 방식을 사용하고, 그 토큰의 저장 위치가 쿠키가 아니라면 CSRF 보호가 불필요한 것이다.

또, Spring Security의 Documentation에 non-browser clients만을 위한 서비스라면 CSRF를 disable 해도 좋다고 명시하고 있다.

 

4. 마지막으로

명확하게 학습하지 않아서 뒤돌면 잊어버렸던 것들을 이번 기회에 완벽하게 이해하였다. 이제는 잊지 말고.. CORS 에러가 발생해도 침착하게 잘 해결해보자..!!!!!!

그리고 csrf disable도 예전에 개발 팀장님께서 "disable 하면 안 되는데?" 갸우뚱 하시는 모습에 시원하게 대답하지 못하고 혼자 헉..했었는데... (흑...ㅋㅋㅋ)

앞으로 누군가 나에게 물어본다면 사용한 이유에 대해서도 명확하게 잘 설명해보자..!!!