이번주는 권한과 보안이다.
패스워드 암호화와 권한이다... 사실 기존의 보안회사에 있을때는
자사의 암호화 라이브러리를 썻기때문에 스프링 시큐리티의 써보는 것은 뭔가 신선했다
(그냥 일반 사용자가 해커가 만든 가짜 사이트 및 링크 클릭하면 해당 인증정보로 공격자가 마음대로 다른 유저의 인증정보를 가지고 서버에 요청을 날려서 해당 유저의 정보의 권한으로 공격자 마음대로한다는 정도? 예를 들어 해당 유저가 운영자였다? 공격자의 아이디의 권한을 운영자 권한으로 수정함)
그래서 다시 정리해볼 겸 정리해보자
CSRF 공격을 이해하는 가장 좋은 방법은 구체적인 예를 살펴보는 것입니다.
은행 웹사이트에서 현재 로그인한 사용자로부터 다른 은행 계좌로 돈을 이체할 수 있는 양식을 제공한다고 가정해 보겠습니다.

예를 들어 이체 양식은 다음과 같을 수 있다 : Transfer HTTP request
<form method="post" action="/transfer"> <input type="text" name="amount"/> <input type="text" name="routingNumber"/> <input type="text" name="account"/> <input type="submit" value="Transfer"/> </form>
해당 HTTP 요청은 다음과 같다:
POST /transfer HTTP/1.1 Host: bank.example.com Cookie: JSESSIONID=randomid Content-Type: application/x-www-form-urlencoded amount=100.00&routingNumber=1234&account=9876
이제 은행 웹사이트에 인증한 다음 로그아웃하지 않고 악의적인 웹사이트를 방문하는 것으로 가정합니다. 악의적인 웹사이트에는 다음과 같은 양식의 HTML 페이지가 포함되어 있습니다:

악마의 전송 폼 : 악마처럼 자기 계좌로 돈 입금되게 하드코딩 해놓음
<form method="post" action="https://bank.example.com/transfer"> <input type="hidden" name="amount" value="100.00"/> <input type="hidden" name="routingNumber" value="evilsRoutingNumber"/> <input type="hidden" name="account" value="evilsAccountNumber"/> <input type="submit" value="Win Money!"/> </form>
돈을 받고 싶어서 제출 버튼을 클릭합니다.
이 과정에서 의도치 않게 악의적인 사용자에게 100달러를 이체하게 됩니다.
악의적인 웹사이트는 사용자의 쿠키를 볼 수 없지만 은행과 관련된 쿠키는 요청과 함께 전송되기 때문에 이런 일이 발생합니다.

더 심각한 문제는 이 모든 과정이 자바스크립트를 사용하여 자동화될 수 있었다는 점입니다. 즉, 사용자가 버튼을 클릭할 필요조차 없었을 수도 있습니다. 또한, 정상 사이트를 방문했다가 XSS 공격을 당하는 경우도 쉽게 발생할 수 있습니다. 그렇다면 이러한 공격으로부터 사용자를 보호하려면 어떻게 해야 할까요?
CSRF 공격이 가능한 이유는 피해자 웹사이트의 HTTP 요청과 공격자 웹사이트의 요청이 정확히 동일하기 때문입니다. 즉, 악의적인 웹사이트의 요청을 거부하고 은행 웹사이트의 요청만 허용할 수 있는 방법이 없습니다. CSRF 공격으로부터 보호하려면 요청에 악의적인 사이트가 제공할 수 없는 무언가가 있는지 확인하여 두 요청을 구분할 수 있어야 합니다.
Spring은 CSRF 공격으로부터 보호하기 위해 두 가지 메커니즘을 제공합니다:
싱크로나이저 토큰 패턴
세션 쿠키에 SameSite 속성 지정하기 (이 블로그에서 설명은 생략한다)
근데 아래와 같은 문구가 있다.
Both protections require that Safe Methods be Idempotent.
(두 가지 보호 기능 모두 안전 메소드가 무력화되어야 합니다.)
번역을 했더니 뭔소리인지 이해 못 하겠다.

간단하게, 웹 애플리케이션에서 안전하다고 여겨지는 메소드들 (예: GET, HEAD 등)은 애플리케이션의 상태나 데이터를 변경하면 안 된다는 것입니다.
기본적인 상태를 바꾸지 않는 메서드는 지키고 이것을 적용하라는 소리인가 보다
CSRF 공격으로부터 보호하는 가장 보편적이고 포괄적인 방법은 싱크로나이저 토큰 패턴을 사용하는 것입니다. 이 솔루션은 각 HTTP 요청에 세션 쿠키 외에 CSRF 토큰이라고 하는 안전한 무작위 생성 값이 HTTP 요청에 포함되도록 하는 것입니다.
HTTP 요청이 제출되면 서버는 예상되는 CSRF 토큰을 조회하고 이를 HTTP 요청의 실제 CSRF 토큰과 비교해야 합니다. 값이 일치하지 않으면 HTTP 요청을 거부해야 합니다.
Synchronizer Token Form
<form method="post"
action="/transfer">
<input type="hidden"
name="_csrf"
value="4bfd1575-3ad1-4d21-96c7-4ef2d9f86721"/>
<input type="text"
name="amount"/>
<input type="text"
name="routingNumber"/>
<input type="hidden"
name="account"/>
<input type="submit"
value="Transfer"/>
</form>
이제 양식에 CSRF 토큰 값이 포함된 숨겨진 입력이 포함됩니다. 동일한 오리진 정책으로 악의적인 사이트가 응답을 읽을 수 없도록 보장하므로 외부 사이트는 CSRF 토큰을 읽을 수 없습니다.
송금에 해당하는 HTTP 요청은 다음과 같습니다:
Synchronizer Token request
POST /transfer HTTP/1.1
Host: bank.example.com
Cookie: JSESSIONID=randomid
Content-Type: application/x-www-form-urlencoded
amount=100.00&routingNumber=1234&account=9876&_csrf=4bfd1575-3ad1-4d21-96c7-4ef2d9f86721
이제 HTTP 요청에 안전한 임의의 값을 가진 _csrf 매개변수가 포함된 것을 확인할 수 있습니다. 악의적인 웹사이트는 _csrf 매개변수의 올바른 값(악의적인 웹사이트에서 명시적으로 제공해야 함)을 제공할 수 없으며, 서버가 실제 CSRF 토큰과 예상 CSRF 토큰을 비교할 때 전송이 실패합니다
실제로 csrf 설정을 적용하면 사용자 페이지에 서버에서 준 csrf 토큰이 박혀있다.
CSRF 보호는 언제 사용해야 하나요? 일반 사용자가 브라우저에서 처리할 수 있는 모든 요청에 CSRF 보호를 사용할 것을 권장합니다. 브라우저를 사용하지 않는 클라이언트만 사용하는 서비스를 만드는 경우 CSRF 보호를 비활성화하는 것이 좋습니다.
그렇다! 브라우저 외의 다른 클라이언트 (예: 모바일 앱, 백엔드 서비스, IoT 디바이스 등)인 경우 가능성이 0% 라고 까지는 못하지만 공격의 주요특성인 브라우져 세션 동작(쿠키사용), 악의적 웹페이지의 요건을 만족시키기 어렵기 때문이다.
지금 서버는 REST API 서버 이기 때문에 (화면을 내려주지 않으므로 csrf 토큰을 박을 수 도 없기 때문에) 스프링 시큐리티의 csrf를 사용하지 않는다!(어차피 REST API는 무상태성을 지켜야하기도 함)

SessionCreationPolicy.STATELESS 설정
Spring Security에서는 세션 생성 정책을 설정할 수 있다
SessionCreationPolicy.STATELESS는 Spring Security에게 세션을 생성하거나 사용하지 말라는
이는 주로 JWT(Json Web Token)와 같은 토큰 기반의 인증 방식에서 사용되며, 서버에서는 사용자에 대한 세션 상태를 유지하지 않습니다. 이로 인해 각 요청은 독립적으로 인증을 가져야 하며, 요청 내에 모든 정보가 포함된다고 한다.
REST API의 특성
REST API는 다음의 핵심 특성이 있다.
- Stateless (무상태): 각 요청이 모든 정보를 포함하며 서버에는 클라이언트의 상태 정보를 저장하지 않는다
- Client의 context를 Server에 저장하지 않는다.(즉, 세션과 쿠키와 같은 context 정보를 신경쓰지 않아도 되므로 구현이 단순해진다)
- Client-Server (클라이언트-서버 구조): 클라이언트와 서버는 독립적으로 동작하며, 각각의 책임이 명확하게 구분됩니다.
- 서버: API를 제공하고 비즈니스 로직 처리 및 저장을 책임진다.
- 클라이언트 : 사용자 인증이나 context(세션, 로그인 정보) 등을 직접 관리하고 책임진다
- Cacheable (캐시 가능): 클라이언트에서 응답을 캐시할 수 있어야 합니다.
- 캐시 사용을 통해 응답시간이 빨라지고 REST Server 트랜잭션이 발생하지 않기 때문에 전체 응답시간, 성능, 서버의 자원 이용률을 향상시킬 수 있다.
- Layered System (계층화 시스템): 클라이언트는 직접적인 서버와 통신할 수도 있고, 중간에 다른 서버를 통해 통신할 수도 있다.
- 중간에 다른계층(다른 서버 혹은 클라이언트)를 유연하게 끼워 넣을 수 있다.
- Uniform Interface (통일된 인터페이스): RESTful 원칙을 지키면서 일관된 인터페이스를 제공해야 합니다.
- URI로 지정한 Resource에 대한 조작을 통일되고 한정적인 인터페이스로 수행한다.
- Code-On-Demand (옵션): 서버에서 클라이언트로 코드를 전송하여 실행할 수 있습니다.
여기서 가장 중요한 특성 중 하나는 Stateless이며, RESTful 서비스는 상태를 저장하지 않아야 합니다. 이는 SessionCreationPolicy.STATELESS 설정과 연관이 있습니다. REST API에서는 각 요청이 독립적으로 처리되어야 하며, 세션과 같은 상태 저장 메커니즘에 의존해서는 안된다.
- Server로부터 스크립트를 받아서 Client에서 실행한다.
이 기회에 그냥 애매하게 알고 있던 REST API 특성도 정리하게 되었다!
지금까지 뭔가 개발하라 그러면 그때그때 마다 그냥 조금 알아보고 흠 이해했군 하고 넘어가면 느낌만 남는것 같다 실제 업무에서도 이런게 많다 경력자라고 이런 안좋은 습관이 든것 같다
진짜 대충알거나 자세히 모르는것을 시간날 때 마다 좀 정성들여 정리하는 습관을 들여야겠다...
좀 큰 단위로 먼가 개발하다보니 커밋을 뭉쳐서 올리게 된다. 좀 더 작은 단위로 코딩하는 습관을 들여야겠다.. 그러니까 하나의 뭉텅이가 크니 그 중간에 push를 못하게 되니 매일 올리지 못하는것 같다 좀더 작업 단위를 작게 나눠서 계획하고 실제 코딩에 들어가야겠다.
업무내용 공유할 때 PPT로 정리하여 공유하는데 기존에 문서에 정리한 것 보다 깔끔하고 정리된 상태여서 굉장히 좋았다 앞으로 이렇게 해야겠다
인프콘에 다녀왔다! 발표 내용도 좋고 윤석님도 보고 인터넷으로 보시던 분들 보니까 신기했다 근데 부스 굿즈가 탐나서 좀 많이 돌아다녔더니 좀 피곤했다 사실 그렇게 필요하진 않았던것같다 다음 컨퍼런스는 부스를 적당히 돌고 체력 분배를 해야겠다.