Java/Spring Boot

Spring Security CSRF 적용하기(With Thymeleaf)

amungstudy 2024. 3. 17. 10:24

Security Config의 @EnableWebSecurity 어노테이션을 통해

csrf 토큰처리가 자동으로 이루어진다.

 

@Configuration
@EnableWebSecurity
@EnableMethodSecurity
@RequiredArgsConstructor
public class SecurityConfig {

 

현재 view 단을 Thymeleaf v3.0.15를 통해 처리중인데 Thymeleaf 를 사용하는 경우  손쉽게 csrf 처리를 할 수 있다.

 

<form action="/login" method="POST" id="login_form">
    <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}">

 

 

 CSRF 적용 후 Logout 404 error 발생

문제 :

csrf공격 처리를 위해 SecurityConfig csrf.disable()코드를 지우고 csrf설정을 활성화했더니, 기존의 a태그로 “/logout”경로로 요청을 보내서 처리하는 로그아웃이 정상 작동하지 않고 404 error가 발생했습니다. (오류 메세지 : “No message available”)

 

해결과정 :

1.     Spring Security 공식문서 확인

공식문서에서 Logging Out에 대한 문서를 확인하였는데, CSRF보호가 활성화된 경우 Spring SecurityLogoutFilter HTTP POST만 처리하도록 구성되어 있다는 것을 확인하였습니다. 따라서 a태그 대신 form태그와 post 요청을 이용해서 로그아웃을 처리했습니다.

 

<th:block sec:authorize="isAuthenticated()">
    <li class="nav-item">
        <form method="post" th:action="@{/logout}">
            <button type="submit">
                <a class="nav-link">로그아웃</a>
            </button>
        </form>
    </li>
</th:block>

 

 


REST API에 CSRF 적용하기

 

GET 요청을 제외한 다른 요청 시 csrf token이 필요하다.

 

html head에 csrf 토큰 정보 입력

<meta name="csrf-token" th:content="${_csrf.token}"/>
<meta name="csrf-header" th:content="${_csrf.headerName}"/>

 

 

api 요청 시 headers에 넣어준다

 

/**
 * HTTP 요청을 보내는 함수
 */
function httpRequest(method,url,body,success,fail){

    //csrf 토큰 정보 가져옴
    let csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
    let csrfHeader = document.querySelector('meta[name="csrf-header"]').getAttribute('content');

    fetch(url, {
        method: method,
        headers: {
            'Content-Type':'application/json',
            [csrfHeader] : csrfToken,
        },
        body: body,
    }).then(response =>{
        if(response.status ===200 || response.status ===201){
            return success();
        }else{
            return fail();
        }

    });
}

 


자동로그인 처리하기

로그인 form태그 내에 추가

<input type="checkbox" class="form-check-input" id="rememberMe" name="rememberMe"/>
<label for="rememberMe" class="form-check-label">로그인 상태 유지</label>

 

SecurityConfig에 내용 추가

.rememberMe() // 자동로그인 설정
    .key("roadAddr") // token생성용 키값
    .rememberMeParameter("rememberMe")// rememeberMe 파라미터 이름
    .tokenValiditySeconds(3600*24*7) //토큰 유지 기간 7일
    .userDetailsService(userService)
    .authenticationSuccessHandler(loginSuccessHandler) // 자동로그인 성공 시 핸들링

 

authenticationSuccessHandler를 구현하는 핸들러를 만들어주고 연결해주어야한다.

 

자동로그인 시 딱히 추가할 로직이 없어서 그냥 redirect만 시켜주었다.

@Configuration
public class RememberMeLoginSuccessHandler implements AuthenticationSuccessHandler {


    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {

        response.sendRedirect("/search");
    }
}