본문 바로가기
Legacy to Microservices

[Legacy to Microservices] JPetStore-6 개선: CSRF 토큰을 JSP에 추가하기

by 뿔난 도비 2025. 2. 16.
반응형

최근에 면접에서 대차게 본 프로젝트에 대해서 설명하고 나서 실제로 꼼질꼼질 프로젝트를 잘 이어나가고 있다. 이전에서는 어려운 분산 트랜잭션에 대해서 개선하는 것을 보여줬었는데 이번 챕터에서는 초보자들도 가능한 CSRF 토큰을 추가하는 개선에 대해서 설명하고자 한다.

 

JPetStore-6 개선: CSRF 토큰을 JSP에 추가하기

 

 

블로그 목차

 

1. CSRF 토큰 추가하기

추천글

 

 

CSRF 토큰 추가하기

 

- CSRF가 무엇인지는 취준 카테고리에서 "Web 관련 면접 준비" 라는 게시글에 설명되어 있다.

- 현재 작업하고 있는 JPetStore-6가 세션 기반의 웹 앱이기 때문에 사용자에게 세션 ID를 쿠키로 발급하는데 이것때문에 CSRF 공격이 일어날 수 있다.

- 따라서 이를 방지하기 위해 CSRF를 추가한다.

 

* 어떤 요청에 추가할까?

- 당연하게도 우리는 어떤 요청에서 추가할지를 고민해야 한다.

- CSRF의 공격 취지상 인증된 사용자가 악의적인 내용을 서버에 보내는 것이기 때문에 로그인 이후에 사용자가 서버에 보내는 요청에 CSRF 토큰을 심어서 서버 측에서 검증하도록 하면 된다.

- 하지만, 모든 요청에 추가할 필요가 있을까?

- 예를 들어, 자원의 변경이 일어나지 않는 GET 요청에 CSRF 토큰을 추가해야 할까?

- 나는 이것은 불필요하다고 생각했기 때문에 인증된 사용자가 POST 메소드로 요청을 보낼 때에만 CSRF 토큰이 보내지도록 설계했다.

- JPetStore-6의 경우 JSP를 사용하고, POST 요청은 모두 Form 태그에서 발생하기 때문에 인증된 사용자가 요청을 보내는 모든 Form에서 CSRF 토큰이 같이 보내지도록 했다.

 

* 어디에 추가할까?

- 다음으로는 어디에 추가할지를 고민한다.

- 대부분의 CSRF 토큰은 input 태그에 hidden 타입으로 숨어있다고 한다.

- 따라서 나도 그렇게 구현했다.

- 예를 들면, 아래와 같다.

<%--

       Copyright 2010-2016 the original author or authors.

       Licensed under the Apache License, Version 2.0 (the "License");
       you may not use this file except in compliance with the License.
       You may obtain a copy of the License at

          http://www.apache.org/licenses/LICENSE-2.0

       Unless required by applicable law or agreed to in writing, software
       distributed under the License is distributed on an "AS IS" BASIS,
       WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
       See the License for the specific language governing permissions and
       limitations under the License.

--%>
<%@ include file="../common/IncludeTop.jsp"%>

<div id="Catalog"><form action="/account/editAccount" method="post">

	<h3>User Information</h3>

	<table>
		<tr>
			<td>User ID:</td>
			<td>${sessionScope.account.username}</td>
			<input type="hidden" name="username" value="${sessionScope.account.username}">
			<input type="hidden" name="csrf" value="${sessionScope.csrf_token}">
		</tr>
		<tr>
			<td>New password:</td>
			<td><input type="password" name="password"></td>
		</tr>
		<tr>
			<td>Repeat password:</td>
			<td><input type="password" name="repeatedPassword"></td>
		</tr>
	</table>
	<%@ include file="IncludeAccountFields.jsp"%>

	<input type="submit" value="Save Account Information">

</form><a href="/order/listOrders">My Orders</a></div>

<%@ include file="../common/IncludeBottom.jsp"%>

- 위 코드 블럭은 EditAccountForm.jsp 이다.

- 로그인된 사용자는 해당 폼에서 사용자 데이터를 수정 가능하다.

- 즉, 자원의 변경이 일어나는 곳이기 때문에 Form 상단에 CSRF 토큰을 삽입했다.

 

* CSRF 토큰의 발급

- 본 웹 앱은 세션 공간을 공유하기 때문에 CSRF 토큰을 관리하기가 쉬웠다.

- CSRF 토큰을 발급하고, 세션에 넣어두면 어떤 서비스에서든 해당 토큰을 가져갈 수 있다.

- 토큰 값은 자바에서 UUID 값을 이용했다.

- 이 값은 어떤 것을 식별할 때 ID 값으로 많이 사용되는데, 중복이 거의 일어나지 않기 때문이라고 한다.

- CSRF 토큰은 로그인 시 생성되어 세션에 저장된다.

@PostMapping("/signon")
public String signon(Account account, HttpServletRequest req, HttpSession session) {
    Account existAccount = accountService.getAccount(account.getUsername(), account.getPassword());

    if (existAccount == null) {
        String value = "Invalid username or password.  Signon failed.";
        req.setAttribute("msg", value);
        session.invalidate();
        return "account/SignonForm";
    } else {
        account.setPassword(null);
        session.setAttribute("csrf_token", UUID.randomUUID().toString());
        session.setAttribute("account", existAccount);
        session.setAttribute("myList", httpFacade.getProductListByCategory(existAccount.getFavouriteCategoryId()));
        session.setAttribute("isAuthenticated", true);
        return "redirect:" + REDIRECT_BASE_URL + "/catalog";
    }
}

- 로그인이 성공했을 때, csrf_token을 키 값으로 세션에 UUID가 담기는 것을 확인할 수 있다.

- 그렇기 때문에 CSRF 토큰을 삽입할 때에는 JSP에서 SessionScope에 접근해서 값을 가져올 수 있다.

- 이전에 나왔던 EditAccountForm.jsp를 보면 위의 설명과 같이 CSRF 토큰을 가져오는 것을 알 수 있다.

 

* CSRF 토큰의 검증

- 이것은 아주 간단한데, RequestParam으로 전달된 토큰 값을 세션에 저장된 토큰 값과 비교하면 된다.

- 일치하면 정상적인 요청이며, 불일치하면 비정상 요청으로 판단한다.

추천글

2025.02.15 - [개발] - [Legacy to Microservices] JPetStore-6에서 주문 처리 분산 트랜잭션 구현

 

[Legacy to Microservices] JPetStore-6에서 주문 처리 분산 트랜잭션 구현

JPetStore-6의 경우 주문 처리를 할 때 다른 서비스의 데이터베이스를 변경해야 하기 때문에 트랜잭션의 원자성을 보장하기 위해서 분산 트랜잭션이 필요하다. 이전 개발 게시글에서 토스의 분산

se-dobby.tistory.com

 

반응형