Side-project : Spring Security + CSRF Token 활용하기(1)

우진·2023년 5월 24일
0
post-thumbnail

👾 Spring Security

https://docs.spring.io/spring-security/reference/index.html


👾 Use CSRF Token In Spring Security

CSRF 공격이란?
Cross-Site Request Forgery
사이트 간 요청 위조의 줄임말.
웹 애플리케이션 취약점 중 하나로 사용자가 자신의 의지와 무관하게 공격자가 의도한 행동을 해서 특정 웹페이지를 보안에 취약하게 한다거나 수정, 삭제 등의 작업을 하게 만드는 공격 방법이다.

이는 사용자의 인증된 세션을 악용하는 공격 방식이다. 세션 로그인을 사용하고 있다면, CSRF 공격에 대한 대책이 어느정도 필요한 것 같다.

그래서 나는 Spring Security에서 제공하는 CSRF Token을 발행해 인증된 사용자의 요청을 재인증할 것이다.


(1) 📁 Update SecurityConfig

@Configuration
@EnableWebSecurity
public class SecurityConfig {

	...

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
                .cors().and()
                //csrf 설정
                .csrf()
                .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()).and()

                .authorizeHttpRequests((authz) -> authz
                        ... )

                .formLogin()
                
               	...

        return http.build();
    }

}
  • .csrf(): Spring Security는 기본적으로 csrf 옵션을 제공한다. 만약 csrf 방어 전략을 사용하고 싶지 않다면 .disable()로 설정하면 된다.
  • .csrfTokenRepository(): 토큰 생성후 이를 저장할 레포를 선택한다.
  • CookieCsrfTokenRepository: 나는 토큰을 브라우저 쿠키에 저장하는 방식을 택했다.
  • .withHttpOnlyFalse(): JS와 함께 사용하는 경우 활성화 시킨다.

(2) 📁 Update HTML

일반적으로 GET 요청일 때에는 토큰을 사용하지 않는다. 그외의 POST, DELETE 등의 요청일 때, 클라이언트 측에서는 요청 헤더에 서버로 부터 전달받은 CSRF Token을 넣어 보내야한다.
만약 요청 헤더에 토큰이 없거나 유효하지 않다면 서버는 403 오류 코드를 반환한다.

내 경우에는, 글을 작성/수정하는 요청이 POST 방식이니 글 작성/수정 폼이 있는 html 파일에 아래와 같이 추가해줬다.

  <!--  csrf token  -->
  <meta name="_csrf" th:content="${_csrf.token}">
  <meta name="_csrf_header" th:content="${_csrf.headerName}"/>

만약 타임리프의 form 요청을 사용하고 있다면 그냥 히든 인풋 태그에 토큰을 넣어 요청하면 된다.

<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>

나는 fetch API를 이용한 비동기 통신을 하고 있어서 전자의 방식을 따랐다.


(3) 📁 Create csrfHeaders.js

fetch API로 요청할 때 헤더에 넣을 내용을 변수로 분리했다. 이 파일을 제일 먼저 import하면 된다.

//csrf token
const csrfHeader = document.querySelector('meta[name="_csrf_header"]').content;
const csrfToken = document.querySelector('meta[name="_csrf"]').content;

//headers 에 csrfToken 설정
//json 타입
const jsonHeaders = {
    "Content-Type": "application/json",
};
jsonHeaders[csrfHeader] = csrfToken;

(4) 📁 Update JavaScript

그리고 적용!

//fetch API
async function savePosting(url) {
    const data = {
        ...
    };

    try {

        const response = await fetch(url, {
            method: 'POST',
      		//csrfHeaders
            headers: jsonHeaders,
            body: JSON.stringify(data)
        });

        //성공
        if(response.status === 200) {
            ...
        }

    } catch(err) {
        console.log(err);
    }
}

ㄴ( ・ㅂ・)ㄱ ㄴ(・ㅂ・ )ㄱ ̑̑ 둠칫 ㄴ( ・ㅂ・)ㄱ ㄴ(・ㅂ・ )ㄱ ̑̑ 두둠칫

이런식으로 모든 요청에 적용시켜주면된다 ~0~
다음 포스팅에서는 엄청나게 헤맸던(...) multipart/form-data 타입일 경우 csrf 토큰을 어떻게 보내야하는지 알아보도록 하겠다!

profile
백 개발을 시작한 응애개발자

1개의 댓글

comment-user-thumbnail
2023년 5월 24일

몬말인지하나도모르갤다

답글 달기