질문, 피드백 등 모든 댓글 환영합니다.
CSRF(Cross-Site Request Forgery)는 사이트간 요청 위조로 웹 애플리케이션의 취약점 중 하나입니다. 자세한 사항은 해당 글 참고해주세요.
스프링 시큐리티는 이러한 csrf 공격을 차단하는 옵션을 제공합니다.
해당 블로그에 따르면 REST API를 기반으로하는 애플리케이션은 굳이 이러한 기능을 활성화 할 필요가 없다고 합니다. HTTP 기반으로 무상태(서버쪽의 세션이나 브라우저 쿠키에 의존하지 않음)로 통신하기 때문에 csrf 공격으로부터 안전하다는 이유입니다.
때문에 thymeleaf가 제공하는 기능으로 간단하게 csrf 설정을 적용해보겠습니다.
먼저 Configurer
에서 csrf().disabled()
를 주석처리합니다.
Configurer
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
// .csrf().disable()
.authorizeRequests()
.antMatchers("/", "/login", "/add", "/error").permitAll()
.antMatchers("/todo/**").authenticated()
.and()
.formLogin()
.loginPage("/login")
.defaultSuccessUrl("/todo")
.and()
.logout()
.logoutSuccessUrl("/");
return http.build();
}
Spring Security CSRF Token 을 기반으로 csrf 공격을 막을 수 있는데 타임리프를 사용하면 html form 요청에 토큰을 자동으로 포함해줍니다.
form 태그에 th:action
을 지정해주어 토큰 값을 요청에 포함할 수 있습니다.
애플리케이션을 실행시키고 페이지 소스를 열어보면 관련된 항목이 생성된 것을 확인할 수 있습니다.
또는 직접 form 태그 안에 csrf 토큰 관련 로직을 작성할 수도 있습니다.
<input type="hidden" th:name="_csrf" th:value="${_csrf.token}"/>
csrf가 활성화 되어있는 상태에서 form 요청 시 4xx 에러를 반환하므로 form 태그가 있는 html을 모두 확인하여 둘 중 하나의 방법으로 csrf 토큰을 생성해줍니다.
기존에는 컨트롤러에서 BindingResult
를 사용하여 error를 생성하였지만 이제는 로그인 처리를 스프링 시큐리티가 담당하므로 다른 방식으로 에러메시지를 출력하겠습니다.
로그인 실패 시 에러메시지를 출력하는 방식을 크게 두 가지가 있습니다.
AuthenticationFailureHandler
를 impliment 하거나 그 구현체인 SimpleUrlAuthenticationFailureHandler
를 extents 받은 객체를 구현하여 빈으로 등록
html에서 파라미터 매핑
스프링 시큐리티 기본 설정에선 로그인 실패 시 기본으로 로그인 경로 + "?error"
경로를 반환합니다. 이는 SecurityFilterChain
의 formLogin()
에서 failureUrl()
으로 직접 설정할 수 있습니다.
저는 쉬운 방법 2번으로 에러 메시지를 출력하겠습니다.
굳이 failureUrl()
으로 설정하지 않고 기본 경로인 "/login?error"
를 사용하겠습니다.
login/form.html
<div th:if="${#fields.hasGlobalErrors()}" th:text="#{loginFail.loginDto}"></div>
-> <div th:if="${param.error}" th:text="#{loginFail.loginDto}"></div>
타임리프 변수 표현식인 $
을 사용하여 파라미터에 접근할 수 있습니다.
지금까지는 핵심 기능을 구현하는데 초점을 맞춰 개발해왔습니다. 때문에 조회 쿼리에 불필요한 데이터가 포함되거나 필요없는 쿼리가 발생하는 경우가 있는데 다음 블로그에선 이를 최적화 해보겠습니다.