Spring Example: ToDo List #13 Spring Security 적용 2

함형주·2022년 12월 1일
0

Spring Example: ToDo

목록 보기
14/16

질문, 피드백 등 모든 댓글 환영합니다.

CSRF + Thymeleaf

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를 생성하였지만 이제는 로그인 처리를 스프링 시큐리티가 담당하므로 다른 방식으로 에러메시지를 출력하겠습니다.

로그인 실패 시 에러메시지를 출력하는 방식을 크게 두 가지가 있습니다.

  1. AuthenticationFailureHandler를 impliment 하거나 그 구현체인 SimpleUrlAuthenticationFailureHandler를 extents 받은 객체를 구현하여 빈으로 등록

  2. html에서 파라미터 매핑

스프링 시큐리티 기본 설정에선 로그인 실패 시 기본으로 로그인 경로 + "?error" 경로를 반환합니다. 이는 SecurityFilterChainformLogin()에서 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>

타임리프 변수 표현식인 $을 사용하여 파라미터에 접근할 수 있습니다.

다음으로

지금까지는 핵심 기능을 구현하는데 초점을 맞춰 개발해왔습니다. 때문에 조회 쿼리에 불필요한 데이터가 포함되거나 필요없는 쿼리가 발생하는 경우가 있는데 다음 블로그에선 이를 최적화 해보겠습니다.


github , 배포 URL (첫 접속 시 로딩이 걸릴 수 있습니다.)

profile
평범한 대학생의 공부 일기?

0개의 댓글