내가 공부하려고 올리는/스프링

스프링 시큐리티(Spring Security) CSRF 설정(AJAX, POST FORM)

결딴력 2022. 7. 15. 17:26
반응형

✅ CSRF란?


CSRF는 'Cross-Site Request Forgery)'의 약자로 '사이트 간 요청 위조'를 뜻합니다.

 

웹 사이트의 취약점을 이용하는 공격의 하나로,

사용자가 자신도 모르게 공격자가 의도한 행위를 특정 웹사이트에 요청하게 만드는 공격을 말합니다.

 

이런 공격 방식은 특정 사용자가 사이트를 신뢰하는 상태를 노리는 공격 방식으로

쿠키 기반 서버의 세션 정보를 획득할 수 있는 상태에서 이뤄집니다.

 

OAuth나 JWT 토큰 등을 이용해 API 기반 로그인을 진행하는 사이트의 경우

세션을 이용하지 않기 때문에 CSRF 방어 전략을 세우지 않기도 합니다.

 

하지만 제가 진행하고 있는 프로젝트의 경우 세션 기반 로그인 기능을 구현했기 때문에

이러한 CSRF 공격에 취약할 수 있습니다.

 

오늘은 이런 CSRF를 방어할 수 있도록 프로젝트를 수정해보겠습니다.

 

 

프로젝트 수정하기


우선 라이브러리를 추가해줍니다.

build.gradle에서 해당 라이브러리를 추가합니다.

 

// https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-security
implementation 'org.springframework.boot:spring-boot-starter-security:2.7.0'

 

다음은 Security 설정 클래스 작성입니다.

저는 SecurityConfig라는 자바 클래스를 만들었습니다.

 

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.headers()
        	//중략
            .and()
                .csrf()
                .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
            .and();

    }
    
//...중략

}

 

☝ @EnableWebSecurity

  • 스프링 시큐리티를 사용하기 위해 다음 어노테이션을 추가합니다.

 

WebSecurityConfigurerAdapter

  • 해당 추상 클래스를 상속하여 Configure 메서드를 구현합니다.

 

csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()

  • 스프링 시큐리티의 경우 기본적으로 CSRF 옵션을 제공합니다.
  • 해당 옵션을 끄고 싶은 경우 'csrf.disable()'을 적용합니다.
  • csrfTokenRepository을 사용하여 'XSRF-TOKEN'이라는 쿠키에 CSRF 토큰을 유지합니다.
  • JS와 함께 사용하는 경우 withHttpOnlyFalse()를 활성화해줍니다.

 

보통 CSRF 옵션은 POST, DELETE, UPDATE에만 적용합니다.

보안 수준이 높은 정보를 가져오는 GET 메서드라면 예외적으로 CSRF 방어 전략을 사용할 수 있습니다.

 

제가 진행하는 프로젝트의 경우 Form 데이터의 경우 POST 메서드에만

CSRF 방어 전략을 적용하기로 했습니다.

 

또한 AJAX로 통신하는 경우

헤더에 토큰 정보를 보내지 않을 경우

'403' 에러가 발생하는 것을 확인했습니다.

 

따라서 AJAX로 발생하는 'GET, POST, UPDATE, DELETE' 메서드 모두

CSRF 방어 전략을 적용하기로 했습니다.

 

 

 

Form 태그와 AJAX에 CSRF 방어 적용


Form 태그입니다.

스프링 시큐리티가 세팅한 CSRF 토큰 값이 세션에 저장되어 있습니다.

이 값을 Form 데이터를 전송할 때 같이 전송해줍니다.

 

다음과 같은 코드를 POST 요청 시 포함하면 됩니다.

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

 

 

AJAX입니다.

우선 HTML 페이지에 토큰 값을 세팅해줍니다.

다음 HTML문을 <header> 태그에 추가해줍니다.

 

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

 

 

다음은 JavaScript입니다.

CSRF 토큰 정보를 변수에 저장합니다.

 

const token = $("meta[name='_csrf']").attr("content")
const header = $("meta[name='_csrf_header']").attr("content");
const name = $("#userName").val();

 

 

마지막으로 AJAX입니다.

$.ajax({
    type:"POST",
    url:"/comments/save/reply/" + data.writer_id,
    dataType : 'json',
    contentType: 'application/json; charset=utf-8',
    data: JSON.stringify(data),
    cache : false,
    
    //추가해야 하는 부분
    beforeSend : function(xhr) {
        xhr.setRequestHeader(header, token);
    },

 

beforeSend로 위와 같이 요청 헤더에 CSRF 토큰 값을 세팅합니다.

 

 

해당 옵션을 적용하면 AJAX로 통신할 경우

'403'에러가 더 이상 발생하지 않습니다.

 

스프링 시큐리티를 이용한 CSRF 토큰 생성을 통한 방어 방식 말고도

서버에서 request의 referrer를 확인하여 도메인이 일치하는지 검증하는

'Referrer 방식'도 있으니 경우에 따라 선택하여 적용하면 될 것 같습니다.

반응형