[Spring Security] 스프링 시큐리티란? & CSRF 토큰

dondonee·2024년 2월 23일
0
post-thumbnail
post-custom-banner

스프링 시큐리티

이번 강의에서는 이전에 만들었던 세션(HttpSession) 기반 로그인 기능에 스프링 시큐리티 프레임워크를 추가해보았다.

스프링 시큐리티는 기본적으로 서버에 영향을 줄 수 있는 POST 등의 요청에서는 시큐리티가 관리하는 토큰(CSRF 토큰)을 가진 요청만 처리하도록 하여 보안 수준을 높인다. 따라서 POST 등의 요청은 CSRF 토큰 정보를 함께 보내도록 기존 코드를 수정했다.



로그인 - 인증과 인가

웹 애플리케이션 개발에서 로그인 처리는 인증과 인가로 나누어 생각할 수 있다 :

  • 인증(Authentication) : 로그인과 비밀번호를 입력받아 올바른 로그인인지 체크하는 것
  • 인가(Authorization) : 사용자를 구분하여 접근 권한을 부여하는 것 (전체 공개/회원 전용 콘텐츠, 관리자 기능 등)

웹 애플리케이션에 사용되는 HTTP는 무상태 프로토콜로, 모든 요청이 서로 독립적이기 때문에 로그인에 성공한 뒤 로그인 상태를 지속하기 위해서는 별도의 방법이 필요하다. 보통 세션(HttpSession)을 사용한다.



스프링 시큐리티란

스프링 시큐리티는 스프링 기반의 어플리케이션의 보안(인증과 권한)을 담당하는 프레임워크이다. 세션을 체크하고 리다이렉트 하는 등의 과정을 편리하게 만들어주고, 보안에 관한 많은 옵션을 제공해준다.

또한 스프링 시큐리티는 필터(filter) 기반으로 동작하기 때문에 스프링 MVC와 독립적으로 동작한다. 디스패처 서블릿이 클라이언트 요청을 받기 전에 기본적으로 제공되는 10개 이상의 스프링 시큐리티 필터를 거친다.



스프링 시큐리티 추가

<properties>
	...
	<org.springsecurity-version>5.0.2.RELEASE</org.springsecurity-version>
</properties>
<dependency>
	<groupId>org.springframework.security</groupId>
	<artifactId>spring-security-web</artifactId>
	<version>${org.springsecurity-version}</version>
</dependency>
<dependency>
	<groupId>org.springframework.security</groupId>
	<artifactId>spring-security-config</artifactId>
	<version>${org.springsecurity-version}</version>
</dependency>
<dependency>
	<groupId>org.springframework.security</groupId>
	<artifactId>spring-security-taglibs</artifactId>
	<version>${org.springsecurity-version}</version>
</dependency>

pom.xml에 스프링 시큐리티의 버전을 지정해주고 관련 의존성을 추가해준다 :

  • spring-security-web
  • spring-security-config
  • spring-security-taglibs


스프링 시큐리티 설정

스프링 시큐리티 설정은 XML 기반 또는 Java Configuration(.java)으로 할 수 있다. 이번 강의에서는 Java Configuration으로 진행했다.


1) 동작 클래스 만들기

public class SecurityInitializer extends AbstractSecurityWebApplicationInitializer {}
  • 클래스명은 정해진 것은 없지만 보통 Initializer라고 한다.
  • AbstractSecurityWebApplicationInitializer를 상속 👉 스프링 시큐리티를 시작하는 클래스 : 스프링 시큐리티의 필터체인을 사용하기 위해 DelegatingFilterProxy를 등록한다.
  • 안에 내용은 비워두면 된다.

2) 설정 클래스 만들기

@Configuration  //환경설정파일
@EnableWebSecurity //스프링 웹 애플리케이션()에서 Spring Security 사용 & 환경설정 파일 명시
public class SecurityConfig extends WebSecurityConfigurerAdapter {

  @Override
  protected void configure(HttpSecurity http) throws Exception {
      CharacterEncodingFilter filter = new CharacterEncodingFilter();
      filter.setEncoding("UTF-8");
      filter.setForceEncoding(true);
      http.addFilterBefore(filter, CsrfFilter.class);
  }
}
  • WebSecurityConfigurerAdapter 클래스 상속
  • 다음 애노테이션을 추가한다 :
    • @Configuration
    • @EnableWebSecurity : 웹 MVC와 스프링 시큐리티를 연결한다고 생각하자.
  • configure(HttpSecurity http) 메서드를 오버라이드 한다. 파라미터가 HttpSecurity인 것을 확인한다.
  • UTF-8 인코딩 필터를 추가한다. 로그인 시 인코딩을 위한 것이다.
    • 인코딩 필터(filter)를 CsrfFilter 앞에 추가한다.

3) 설정 클래스 등록

public class WebConfig extends AbstractAnnotationConfigDispatcherServletInitializer {

	@Override
  protected Class<?>[] getRootConfigClasses() {
      return new Class[]{RootConfig.class, SecurityConfig.class};  //배열 -> 여러 설정 클래스 등록 가능
  }
}

WebConfig 설정 클리스에 스프링 시큐리티 설정 클래스(SecurityConfig.class)를 등록한다.



CSRF 토큰

세션 기반으로 만든 로그인 기능에 스프링 시큐리티를 추가한 뒤 다시 테스트 해보니, 위와 같이 403 Forbidden 오류가 발생했다.

스프링 시큐리티는 CSRF 공격을 방지하기 위해서 CSRF 토큰을 가진 요청만 처리하도록 방어하고있기 때문이다. 서버에 중요한 영향을 끼칠 수 있는 POST 방식(HTML form, AJAX)은 토큰을 체크한다. GET 방식은 토큰을 체크하지 않는다.


CSRF 공격

  • CSRF(Cross-Site Request Forgery) : 사이트간 요청 위조
  • 정상적인 사용자가 의도와 상관없이 서버를 공격하게 만드는 방법
  • 세션 로그인을 사용하는 서버에서 정상적으로 인증을 받은 사용자가 악성 스크립트를 실행하게끔 하여 서버가 악의적인 요청을 처리하도록 하는 방법. 단, 공격자가 요청 방식에 대해 알고 있어야 한다.

CSRF 토큰 동작

사이트간 위조 방지를 목적으로 특정한 값의 토큰을 사용하여 요청을 검증하는 방식. 동작은 세션과 비슷하다.

  1. 서버가 접속한 클라이언트에게 특정 CSRF 토큰을 전달한다.
  2. 클라이언트는 서버에 접속 할 때마다 CSRF 값을 가지고 온다. (HTTP 헤더-값으로 전달)
  3. 서버는 클라이언트의 CSRF 값과 서버에 보관된 CSRF값 을 비교하여
  4. 동일한 사용자 접속인지 확인하고 서비스를 제공해준다.

CSRF 토큰 보내기

  • HTML form 요청의 경우 : <input type=”hidden”>으로 보낸다.
  • HTML form 요청 & 멀티파트 데이터의 경우(enctype=”multipart/form-data”) : URL 파라미터로 보낸다.
  • Ajax 요청의 경우 : xhr.setRequestHeader()로 HTML 요청에 헤더-토큰값을 담아 보낸다.

폼에서 토큰 넘기기

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

회원가입 폼(join.jsp)에서 위와 같이 <form> 내에 히든 태그를 통해 HTTP로 폼 데이터와 함께 토큰 값을 전달해 준다.

페이로드를 보면 회원 정보와 함께 토큰 값(_csrf)이 같이 전송된 것을 확인할 수 있다.


스프링 시큐리티 인코딩

그런데 DB에 저장된 값을 보면 한글이 깨져있다.

이전에 WebCofig.java에서 UTF-8 인코딩 필더를 넣어주었지만 스프링 시큐리티를 적용하면 별도의 인코딩 설정이 필요하다.

@Override
protected void configure(HttpSecurity http) throws Exception {
    CharacterEncodingFilter filter = new CharacterEncodingFilter();
    filter.setEncoding("UTF-8");
    filter.setForceEncoding(true);
    http.addFilterBefore(filter, CsrfFilter.class);
}

위와 같이 SecurityConfig.java에도 UTF-8 인코딩 설정을 추가해주면 된다.


멀티파트 데이터(폼) 토큰 넘기기

<form action="${contextPath}/memImageUpdate.do?${_csrf.parameterName}=${_csrf.token}" 
	method="post" enctype="multipart/form-data">

파일 업로드(enctype="multipart/form-data")의 경우에는 히든 태그가 아니라 요청 URL에서 파라미터로 토큰을 넘겨야 한다. 요청 URL 뒤에 ?${_csrf.parameterName}=${_csrf.token}를 추가하자.


Ajax에서 토큰 넘기기

<script type="text/javascript">
    var csrfHeaderName = "${_csrf.headerName}";
    var csrfTokenValue = "${_csrf.token}";

JS 영역 상단에 위와 같이 CSRF 토큰의 헤더와 값을 글로벌 변수로 추가해주자.

function boardList(){
	$.ajax({
		url : "${cpath}/board",
		type : "get",
		dataType : "json",
		beforeSend: function(xhr){
			xhr.setRequestHeader(csrfHeaderName, csrfTokenValue)
		},
		success : callBack,
		error : function(){ alert("error"); }
	});
}

Ajax + POST 방식을 사용하는 요청에는 위와 같이 beforesend를 추가하여 토큰 헤더를 설정해주자. 서버에 영향을 끼치는 PUT, DELETE 메서드도 POST 방식과 마찬가지로 Ajax 요청 시 beforesend 처리해주도록 한다.




🔗 References

post-custom-banner

0개의 댓글