이번 강의에서는 이전에 만들었던 세션(HttpSession
) 기반 로그인 기능에 스프링 시큐리티 프레임워크를 추가해보았다.
스프링 시큐리티는 기본적으로 서버에 영향을 줄 수 있는 POST 등의 요청에서는 시큐리티가 관리하는 토큰(CSRF 토큰)을 가진 요청만 처리하도록 하여 보안 수준을 높인다. 따라서 POST 등의 요청은 CSRF 토큰 정보를 함께 보내도록 기존 코드를 수정했다.
웹 애플리케이션 개발에서 로그인 처리는 인증과 인가로 나누어 생각할 수 있다 :
웹 애플리케이션에 사용되는 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으로 진행했다.
public class SecurityInitializer extends AbstractSecurityWebApplicationInitializer {}
AbstractSecurityWebApplicationInitializer
를 상속 👉 스프링 시큐리티를 시작하는 클래스 : 스프링 시큐리티의 필터체인을 사용하기 위해 DelegatingFilterProxy
를 등록한다.@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
인 것을 확인한다.filter
)를 CsrfFilter
앞에 추가한다.public class WebConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[]{RootConfig.class, SecurityConfig.class}; //배열 -> 여러 설정 클래스 등록 가능
}
}
WebConfig
설정 클리스에 스프링 시큐리티 설정 클래스(SecurityConfig.class
)를 등록한다.
세션 기반으로 만든 로그인 기능에 스프링 시큐리티를 추가한 뒤 다시 테스트 해보니, 위와 같이 403 Forbidden 오류가 발생했다.
스프링 시큐리티는 CSRF 공격을 방지하기 위해서 CSRF 토큰을 가진 요청만 처리하도록 방어하고있기 때문이다. 서버에 중요한 영향을 끼칠 수 있는 POST 방식(HTML form, AJAX)은 토큰을 체크한다. GET 방식은 토큰을 체크하지 않는다.
사이트간 위조 방지를 목적으로 특정한 값의 토큰을 사용하여 요청을 검증하는 방식. 동작은 세션과 비슷하다.
<input type=”hidden”>
으로 보낸다.enctype=”multipart/form-data”
) : URL 파라미터로 보낸다.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}
를 추가하자.
<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
처리해주도록 한다.