[스프링 시큐리티] 7. 기본 API 및 Filter 이해(CSRF(사이트간 요청 위조))
해당 포스팅은 인프런에서 스프링 시큐리티 정수원님의 강의를 참고하여 작성했습니다.
스프링 시큐리티 - Spring Boot 기반으로 개발하는 Spring Security - 인프런 | 강의
축하합니다! 아이폰에 당첨되셨습니다.!!!!
여러분은 웹 서핑 중에 아래와 같은 웹사이트에 접속 된 경험이 있을 것입니다.
갑자기 아이폰 당첨이라니?! 이와 같은 웹사이트를 클릭하면 당신은 csrf 에 노출된 것일 수도 있습니다.
csrf란 무엇일까요?
CSRF란?
https://nordvpn.com/ko/blog/csrf/
Cross-Site Request Forgery
의 약어로 우리말로 해석하면 악성 웹사이트 공격 유형(크로스 사이트 요청 위조
) 입니다.CSRF
는 인증된 사용자의 브라우저에서 사이트가 갖는 신뢰를 악의적인 공격에 사용합니다.
쉽게 이야기하면, 공격자가 당신에게(당신은 특정 웹사이트에 로그인한 상태 입니다.) 인증받은 웹 애플리케이션에 특정 요청을 보내도록 유도하는 공격 행위를 의미합니다.
그렇다면 XSS란?
XSS
와 CSRF
는 사용자의 브라우저를 대상으로 한다는 공통점이 있습니다.
차이점은 XSS
는 인증된 세션 없이도 공격을 진행하는 방식이고CSRF
는 사용자의 인증된 세션을 악용하는 공격 방식 입니다.
XSS
은 사용자가 특정 사이트를 신뢰한다는 사실을 이용한 공격 방식이지만,CSRF
는 웹 애플리케이션이 인증된 사용자의 요청을 신뢰한다는 사실을 이용한 공격 방식입니다.
또한 XSS
은 사용자에서 스크립트가 실행되지만 CSRF
는 서버에서 스크립트가 실행된다는 차이점이 있습니다.
정리하면!XSS
는 사용자 PC에서 스크립트를 실행해 사용자의 정보를 탈취하는 것을 목적으로 하는 반면,CSRF
는 요청을 위조함으로써 사용자 몰래 송금과 제품 구입 등 특정 행위를 수행하는 것을 목적으로 합니다.
CSRF
는 어떻게 공격을 유도할까요?
CSRF 공격 과정
사용자가 온라인 쇼핑몰에 로그인하고 쿠키가 발급되면 사용자의 브라우저는 온라인 쇼핑몰이 신뢰하는 상황입니다.
이때 사용자가 공격자가 보낸 링크를 클릭하게 되면,
사용자의 브라우저에서 공격자의 페이지에 접속하게 되고
아이폰 받기를 누르게 되면 사용자의 브라우저를 통해서 온라인 쇼핑몰에 요청하게 되는 것 입니다.
CSRF 방지
스프링 Security CSRF 토큰 이야기 - 그래서 개발자는 뭘 하면 되죠 | 노란_병아리
일반적으로 CSRF 토큰
을 통해 요청이 사용자가 전송한 것이 맞는지 확인하거나 재인증을 요구하는 조치를 취하고 있습니다.
그렇다면 스프링 시큐리티는 어떻게 CSRF
를 방지 할까요?
CSRF 방지 과정
- 요청(
POST
,PUT
,PATCH
,DELETE
)에 랜덤하게 생성된 토큰을HTTP
파라미터로 요구 - 요청 시 전달되는 토큰 값과 서버에 저장된 실제 값과 비교한 후 만약 일치하지 않으면 요청은 실패
스프링 시큐리티에서 csrf
를 설정하는 방법은 아래와 같습니다.
csrf 토큰
을 만들면 유저의 세션
에 저장(HttpSessionCsrfTokenRepository
)
@EnableWebSecurity
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest().authenticated();
http.formLogin();
http
.csrf().csrfTokenRepository(httpSessionCsrfTokenRepository());
}
@Bean
public HttpSessionCsrfTokenRepository httpSessionCsrfTokenRepository() {
return new HttpSessionCsrfTokenRepository();
}
}
csrf 토큰
을 만들면 브라우저 쿠키
에 저장(CookieCsrfTokenRepository
)
해당 방법은 쿠키
에 csrf 토큰
을 담아 보내고 쿠키
에서 csrf 토큰
을 추출하여 검증하기 때문에 공격자가 사용자의 쿠키를 탈취하면 보안에 문제가 발생할 수 있습니다. 😭
@EnableWebSecurity
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest().authenticated();
http.formLogin();
http
.csrf().csrfTokenRepository(cookieCsrfTokenRepository());
}
@Bean
public CookieCsrfTokenRepository cookieCsrfTokenRepository() {
return new CookieCsrfTokenRepository();
}
}
CSRF 방지 실습
HttpSessionCsrfTokenRepository
csrf토큰
을 세션에 저장하여 관리하는 방식 입니다.
CsrfFilter
에서 토큰을 생성할때 repositroy
에 토큰을 저장합니다.repository
의 구현체인 HttpSessionCsrfTokenRepository
의 saveToken()
에서 HttpSession
에 토큰 값을 저장합니다.
MySecurityConfig
@EnableWebSecurity
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().permitAll();
http
.formLogin();
http
.csrf().csrfTokenRepository(httpSessionCsrfTokenRepository());
}
@Bean
public HttpSessionCsrfTokenRepository httpSessionCsrfTokenRepository() {
HttpSessionCsrfTokenRepository csrfRepository = new HttpSessionCsrfTokenRepository();
// 기본값이 "org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository.CSRF_TOKEN" 입니다.
// 기본값이 너무 길어서 따로 설정하는것이 좋습니다.
csrfRepository.setSessionAttributeName("CSRF_TOKEN");
return csrfRepository;
}
}
TestController
import org.springframework.security.web.csrf.DefaultCsrfToken;
@RestController
public class TestController {
@GetMapping("/csrf")
public String getOrCreateCsrfToken(HttpServletRequest request) {
HttpSession session = request.getSession();
DefaultCsrfToken csrfToken = (DefaultCsrfToken) session.getAttribute("CSRF_TOKEN");
return csrfToken.getHeaderName()+":"+csrfToken.getToken();
}
@PostMapping("/csrf")
public String getOrCreateCsrfToken() {
return "HttpSessionCsrfTokenRepository";
}
}
결과
먼저 GET
요청으로 csrf 토큰
을 얻습니다.
csrf 토큰
을 헤더에 입력한 뒤 POST
요청을 합니다.
이때 헤더는 X-CSRF-TOKEN
입니다.
CookieCsrfTokenRepository
csrf토큰
을 쿠키에 저장하여 관리하는 방식입니다.
MySecurityConfig
@EnableWebSecurity
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().permitAll();
http
.formLogin();
http
.csrf().csrfTokenRepository(cookieCsrfTokenRepository());
}
@Bean
public CookieCsrfTokenRepository cookieCsrfTokenRepository() {
return new CookieCsrfTokenRepository();
}
}
TestController
@RestController
public class TestController {
@GetMapping("/cookie/csrf")
public String testGET(HttpServletRequest request) {
Cookie cookie = WebUtils.getCookie(request, "XSRF-TOKEN");
return cookie.getName()+":"+cookie.getValue();
}
@PostMapping("/cookie/csrf")
public String testPost() {
return "CookieCsrfTokenRepository";
}
}
결과
먼저 GET
요청으로 csrf 토큰
을 얻습니다.
csrf 토큰
을 헤더에 입력한 뒤 POST
요청을 합니다.
이때 헤더는 X-XSRF-TOKEN
입니다.
csrf 토큰
이 쿠키
에 저장된것을 확인 할 수 있습니다.