[Spring]Spring security - CSRF란?, http.csrf().disable() ?

hyewon jeong·2022년 12월 28일
5

Spring

목록 보기
6/65

🎈이 게시글은 공부를 위해 https://velog.io/@woohobi/Spring-security-csrf%EB%9E%80 참고하여 필요한 부분은 더 추가하여 작성하였습니다.

스프링 심화강의를 들으며 Spring security 에 대해 공부를 하는데
CSRF는 무엇인지 알겠는데 왜 CSRF 요청을 프로텍트 하기 위함이면 사용해야 될텐데 왜 disable로 하는지 의문이 생겼다 .

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        // CSRF 설정
        http.csrf().disable();

http.csrf().disable()에서 csrf은 무엇이고, disable() 하는 이유가 무엇일까?

CSRF

Cross site Request forgery로 사이트 간 위조 요청인데,

  • 공격자가 인증된 브라우저에 저장된 쿠키의 세션 정보를 활용하여 웹 서버에 사용자가 의도하지 않은 요청을 전달하는 것으로
    즉 정상적인 사용자가 의도치 않은 위조요청을 보내는 것을 의미한다.

예를 들어 A라는 도메인에서, 인증된 사용자 H가 위조된 request를 포함한 link, email을 사용하였을 경우(클릭, 또는 사이트 방문만으로도), A 도메인에서는 이 사용자가 일반 유저인지, 악용된 공격인지 구분할 수가 없다.

CSRF protection은 spring security에서 default로 설정된다.

즉, protection을 통해 GET요청을 제외한 상태를 변화시킬 수 있는 POST, PUT, DELETE 요청으로부터 보호한다.

  • csrf protection을 적용하였을 때, html에서 다음과 같은 csrf 토큰이 포함되어야 요청을 받아들이게 됨으로써, 위조 요청을 방지하게 됩니다.
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>

Rest api에서의 CSRF

그래서 이렇게 보안 수준을 향상시키는 CSRF를 왜 disable 하였을까? spring security documentation에 non-browser clients 만을 위한 서비스라면 csrf를 disable 하여도 좋다고 한다.

📢이 이유는 rest api를 이용한 서버라면, session 기반 인증과는 다르게 stateless하기 때문에 서버에 인증정보를 보관하지 않는다. rest api에서 client는 권한이 필요한 요청을 하기 위해서는 요청에 필요한 인증 정보를(OAuth2, jwt토큰 등)을 포함시켜야 한다. 따라서 서버에 인증정보를 저장하지 않기 때문에 굳이 불필요한 csrf 코드들을 작성할 필요가 없다.


예시
송금을 담당하는 아래와 같은 transfer 함수가 있다고 할 때, H 사용자가 A 사이트에서 쿠키,세션 등으로 인증된 경우 위조된 요청이 들어왔을 때, 해당 요청이 실행된다.

@PostMapping("/transfer")
public void transfer2(@RequestParam int accountNo, @RequestParam final int amount) {
    log.warn("accountNo: {} , amount:{}", accountNo, amount);
    ...
}

또, 아래와 같은 위조된 요청을 보낼 수 있는 코드를 가진 사이트에 일반 유저가 들어가게 된다면 img 태크에 포함된 url 요청으로 인해 위조된 GET 요청을 서버로 보낼 것이고, submit 버튼을 누른다면 서버로 위조된 post 요청을 보내게 될 것이다.

   //fakeBank.html
    <img src="http://localhost:8080/transfer?accountNo=5678&amount=1000"/>

    <form action="http://localhost:8080/transfer" method="POST">
        <input name="accountNo" type="hidden" value="5678"/>
        <input name="amount" type="hidden" value="1000"/>
        <input type="submit" value="Show Kittens Picture">
    </form>

http//localhost:8080/fake 에서 버튼을 누른다면 서버에 post요청을 보내게 되는데,

위조된 post요청이 서버에서 이를 구별하지 못하고 post요청을 그대로 실행하게 된다.

📢따라서 이를 방지하기 위해, spring security에서는 기본적으로 csrf protection을 제공한다.

스프링 시큐리티 적용하는 방법

Gradle에 spring-boot-starter security를 추가하고, 아래와 같은 SecurityConfig를 클래스를 만들어준다면, csrf 공격으로부터 방지한다.

'스프링 시큐리티'프레임워크 추가

build.gradle

implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-security'
@EnableWebSecurity
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {}

http//localhost:8080/fake 에서 버튼을 다시 누른다면 403으로 forbidden 요청을 반환한다.

하지만, 우리가 만들고자 하는 rest api에서는 csrf 공격으로부터 안전하고 매번 api 요청으로부터 csrf 토큰을 받지 않아도 되어 이 기능을 disable() 하는 것이 더 좋은 판단으로 보인다.

@EnableWebSecurity
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable();
    }
}

'스프링 시큐리티' 활성화 하기

REST API 에서의[ WebSecurityConfig (springboot 2.7이상) ]

package com.sparta.springsecurity.config;


import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@EnableWebSecurity // 스프링 Security 지원을 가능하게 함
public class WebSecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        // CSRF 설정
        http.csrf().disable();
        
        http.authorizeRequests().anyRequest().authenticated();

        // 로그인 사용
        http.formLogin();
        
        return http.build();
    }

}

[ WebSecurityConfig ]
참고링크

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configuration
@EnableWebSecurity // 스프링 Security 지원을 가능하게 함
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http.authorizeRequests()
                // image 폴더를 login 없이 허용
                .antMatchers("/images/**").permitAll()
                // css 폴더를 login 없이 허용
                .antMatchers("/css/**").permitAll()
                // 어떤 요청이든 '인증'
                .anyRequest().authenticated()
                .and()
                    // 로그인 기능 허용
                    .formLogin()
                    .loginPage("/user/login")
                    .defaultSuccessUrl("/")
                    .failureUrl("/user/login?error")
                    .permitAll()
                .and()
                    // 로그아웃 기능 허용
                    .logout()
                    .permitAll();
    }
}


**SpringBoot 3v 변경된 코드 확인 authorizeRequests() → authorizeHttpRequests()**
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        // CSRF 설정
        http.csrf().disable();

        http.authorizeHttpRequests().anyRequest().authenticated();

        // 로그인 사용
        http.formLogin();

        return http.build();
    }

🎈참고자료

profile
개발자꿈나무

0개의 댓글