WeniVooks

검색

웹/네트워크/HTTP 베이스캠프

클라이언트 상태 저장

웹 애플리케이션에서 상태를 저장하는 방법에는 크게 클라이언트 측에서 상태를 저장하는 방법과 서버 측에서 상태를 저장하는 방법이 있습니다. 클라이언트 측에서는 쿠키와 로컬 스토리지를 사용하고, 서버 측에서는 세션을 사용하는 것이 일반적입니다.

웹 브라우저의 저장공간은 지원이 종료되는 경우도 있습니다. 예를 들어 google chrome의 경우 23년 12월 web sql 지원 종료로 https://sqlschool.co.kr/ 와 같은 서비스가 작동하지 않는 일도 발생하게 되었습니다. 따라서 어떤 기술을 사용할 때 항상 브라우저가 그 기술을 지원할 것이라 가정하는 것은 옳지 않습니다. 새로운 이슈나 새로운 기술의 등장으로 일반적인 방법이 변할 수도 있습니다.

9.1 클라이언트 상태 저장

9.1.1 쿠키(Cookie)

쿠키는 웹 브라우저에 저장되는 작은 텍스트 파일로, 키-값 쌍으로 데이터를 저장합니다. 쿠키는 서버에서 응답 헤더에 Set-Cookie에 값을 설정하여 브라우저로 전달하면 브라우저는 서버에서 받은 헤더 값을 브라우저에 쿠키로 저장하며 이는 document.cookie로 접근할 수 있습니다. 이렇게 저장된 쿠키는 사용자가 서버로 어떠한 요청을 보낼 때마다 별도의 작업을 하지 않아도 자동으로 쿠키의 내용을 요청 헤더에 추가하여 전달하게 됩니다. 쿠키는 도메인 기준으로 저장이 되기 때문에 aaa.co.kr에서 저장된 쿠키가 bbb.co.kr로 넘어가진 않습니다.

about:blank 페이지에서는 작동하지 않으니 간단한 html파일을 라이브서버로 열어 실습하도록 하겠습니다.

JavaScript에서 쿠키를 설정하는 예제 코드

// 예제1
// 쿠키 설정
document.cookie = "username=jun";
 
// 쿠키 읽기
const cookieValue = document.cookie;
console.log(cookieValue);
 
// 쿠키 추가
document.cookie = "age=10";
 
cookieValue
// 예제1
// 쿠키 설정
document.cookie = "username=jun";
 
// 쿠키 읽기
const cookieValue = document.cookie;
console.log(cookieValue);
 
// 쿠키 추가
document.cookie = "age=10";
 
cookieValue
// 예제2
// 쿠키 설정
document.cookie = "username=jun";
 
// 쿠키 읽기
document.cookie
 
// 쿠키 변경
document.cookie = "username=juni";
 
// 쿠키 추가
document.cookie = "age=10";
 
// 쿠키 읽기
document.cookie
 
// 쿠키 삭제
document.cookie = "username=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;";
 
// 쿠키 읽기
document.cookie
// 예제2
// 쿠키 설정
document.cookie = "username=jun";
 
// 쿠키 읽기
document.cookie
 
// 쿠키 변경
document.cookie = "username=juni";
 
// 쿠키 추가
document.cookie = "age=10";
 
// 쿠키 읽기
document.cookie
 
// 쿠키 삭제
document.cookie = "username=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;";
 
// 쿠키 읽기
document.cookie

첫번째 예제에서는 쿠키값이 갱신되지 않는 것을 확인할 수 있습니다. 이렇게 특정 변수에 할당하여 사용하게 되면 상황에 따라 특정 시점에 쿠키값만 가지고 있을 수 있으니 주의해주세요. 두번째 예제에서는 쿠키 값을 변경하면 갱신되는 것을 확인할 수 있습니다. 만약 쿠키를 삭제하고 싶다면 만료 기간을 과거로 설정하면 즉시 삭제할 수 있게 됩니다.

  • username=은 삭제하려는 쿠키의 이름을 지정합니다. 여기서는 username 쿠키를 삭제합니다.
  • expires=Thu, 01 Jan 1970 00:00:00 UTC는 쿠키의 만료 시간을 과거로 설정합니다. 이렇게 하면 쿠키가 즉시 만료되어 삭제됩니다.
  • path=/는 쿠키의 경로를 지정합니다. 루트 경로(/)로 설정하면 해당 도메인의 모든 경로에서 쿠키가 삭제됩니다.

쿠키를 삭제할 때는 쿠키의 이름과 경로를 정확히 지정해야 합니다. 쿠키의 이름과 경로가 일치하지 않으면 삭제되지 않습니다.

쿠키값은 옵션값을 세미콜론으로 구분하기 때문에 여러 값을 한번에 넣을 때 세미콜론으로 구분하여 값을 넣지 않도록 합니다. 예를 들어 아래와 같은 값은 제대로 입력되지 않습니다.

document.cookie = "username=jun; age=10;"

Python에서 쿠키를 설정하는 예제 코드

from http.server import HTTPServer, SimpleHTTPRequestHandler
from http import cookies
 
class CookieHandler(SimpleHTTPRequestHandler):
    def do_GET(self):
        if self.path == "/":
            self.send_response(200)
            self.send_header("Content-type", "text/html")
 
            # 쿠키 설정
            self.send_header("Set-Cookie", "username=jun")
            self.send_header("Set-Cookie", "age=10")
 
            self.end_headers()
            self.wfile.write(b"<html><body><h1>Cookie has been set!</h1></body></html>")
        elif self.path == "/read":
            self.send_response(200)
            self.send_header("Content-type", "text/html")
            self.end_headers()
 
            # 쿠키 읽기
            if "Cookie" in self.headers:
                cookie_string = self.headers["Cookie"]
                cookie = cookies.SimpleCookie()
                cookie.load(cookie_string)
 
                username = cookie["username"].value
                age = cookie["age"].value
 
                self.wfile.write(f"<html><body><h1>Username: {username}, Age: {age}</h1></body></html>".encode())
            else:
                self.wfile.write(b"<html><body><h1>No cookies found!</h1></body></html>")
        else:
            self.send_error(404)
 
def run(server_class=HTTPServer, handler_class=CookieHandler, port=8000):
    server_address = ("", port)
    httpd = server_class(server_address, handler_class)
    print(f"Starting server on port {port}...")
    httpd.serve_forever()
 
if __name__ == "__main__":
    run()
from http.server import HTTPServer, SimpleHTTPRequestHandler
from http import cookies
 
class CookieHandler(SimpleHTTPRequestHandler):
    def do_GET(self):
        if self.path == "/":
            self.send_response(200)
            self.send_header("Content-type", "text/html")
 
            # 쿠키 설정
            self.send_header("Set-Cookie", "username=jun")
            self.send_header("Set-Cookie", "age=10")
 
            self.end_headers()
            self.wfile.write(b"<html><body><h1>Cookie has been set!</h1></body></html>")
        elif self.path == "/read":
            self.send_response(200)
            self.send_header("Content-type", "text/html")
            self.end_headers()
 
            # 쿠키 읽기
            if "Cookie" in self.headers:
                cookie_string = self.headers["Cookie"]
                cookie = cookies.SimpleCookie()
                cookie.load(cookie_string)
 
                username = cookie["username"].value
                age = cookie["age"].value
 
                self.wfile.write(f"<html><body><h1>Username: {username}, Age: {age}</h1></body></html>".encode())
            else:
                self.wfile.write(b"<html><body><h1>No cookies found!</h1></body></html>")
        else:
            self.send_error(404)
 
def run(server_class=HTTPServer, handler_class=CookieHandler, port=8000):
    server_address = ("", port)
    httpd = server_class(server_address, handler_class)
    print(f"Starting server on port {port}...")
    httpd.serve_forever()
 
if __name__ == "__main__":
    run()

브라우저에서 http://localhost:8000/으로 접속하면 쿠키가 설정됩니다. 그 후에 http://localhost:8000/read로 접속하면 설정된 쿠키의 값을 읽어와 출력하는 것을 확인할 수 있습니다.

do_GET 메서드 내에서 루트 경로("/")에 대한 요청을 처리할 때, Set-Cookie 헤더를 사용하여 username과 age 쿠키를 설정합니다. 다시 접속할 때에는 뒤에 /read 경로를 추가해서 접속하면 요청 헤더에 쿠키가 있는지 확인하여 파싱한다음 출력합니다.

요약

  • 쿠키는 키-값 쌍의 형태로 저장되며, 도메인 단위로 관리됩니다. 따라서 특정 도메인에서 발행한 쿠키는 해당 도메인 내에서만 유효합니다.
  • 쿠키의 크기는 제한적이며, 보통 4KB 이하로 제한됩니다. 따라서 대량의 정보를 저장하기에는 적합하지 않습니다.
  • 쿠키는 브라우저 종료가 될 때 사라지는 세션 쿠키와 만료 기간이 종료되어야만 사라지는 영구 쿠키가 있습니다.
  • 쿠키는 서비스 운영자가 발행하는 퍼스트 파티 쿠키서드 파티 쿠키가 있는데 서드 파티 쿠키의 경우 광고 같은 곳에서 활용됩니다.
  • 쿠키는 브라우저에 저장되기 때문에, 사용자가 쿠키를 삭제하거나 차단할 수 있습니다. 따라서 중요한 정보는 서버에서 관리하는 것이 좋습니다.
  • 보안상 민감한 정보는 쿠키에 저장하지 않는 것이 좋습니다. 쿠키는 네트워크 상에서 평문으로 전송되므로, 중간에 탈취될 위험이 있기 때문입니다. 민감한 정보는 세션이나 토큰 등의 방식으로 서버에서 관리하는 것이 안전합니다. 쿠키는 CSRF(Cross-Site Request Forgery) 공격에 취약할 수 있습니다. CSRF 공격은 쿠키가 요청에 자동으로 포함되는 것을 이용하여 사용자가 로그인한 웹사이트의 권한을 도용하여, 사용자의 의도와는 무관하게 공격자가 원하는 요청을 수행하게 만드는 공격 방식입니다.
9.1.2 로컬 스토리지(Local Storage)

로컬 스토리지는 웹 브라우저에서 제공하는 저장소로, 키-값 쌍으로 데이터를 저장할 수 있습니다. 로컬 스토리지는 쿠키와 달리 서버로 전송되지 않으며, 웹 페이지가 닫혀도 데이터가 유지됩니다.

해당 예제는 콘솔창에서도 실행해볼 수 있습니다.

JavaScript에서 로컬 스토리지를 사용하는 예제 코드

// 로컬 스토리지에 데이터 저장
localStorage.setItem('username', 'John Doe');
 
// 로컬 스토리지에서 데이터 읽기
const username = localStorage.getItem('username');
console.log(username);
// 로컬 스토리지에 데이터 저장
localStorage.setItem('username', 'John Doe');
 
// 로컬 스토리지에서 데이터 읽기
const username = localStorage.getItem('username');
console.log(username);

요약

  • 로컬스토리지는 브라우저에 데이터를 저장하는 웹 스토리지의 한 종류로, 쿠키와 달리 만료 기간이 없습니다. 명시적으로 삭제하지 않는 한 데이터가 영구적으로 보관됩니다.
  • 로컬스토리지도 키-값 쌍의 형태로 데이터를 저장하며, 도메인 단위로 관리됩니다. 해당 도메인 내에서만 데이터에 접근할 수 있습니다.
  • 로컬스토리지의 저장 용량은 브라우저마다 차이가 있지만, 일반적으로 5MB 이상을 제공합니다. 쿠키에 비해 훨씬 많은 양의 데이터를 저장할 수 있습니다.
  • 로컬스토리지는 쿠키와 달리 서버로 자동 전송되지 않습니다. 필요한 경우 JavaScript를 통해 명시적으로 서버로 전송해야 합니다.
  • 로컬스토리지는 사용자가 직접 삭제할 수 있으며, 브라우저 설정에서 데이터 저장을 차단할 수도 있습니다. 따라서 중요한 데이터는 서버에서 관리하는 것이 좋습니다.
  • 로컬스토리지에 민감한 정보를 저장하는 것은 권장되지 않습니다. 로컬스토리지의 데이터는 JavaScript를 통해 접근할 수 있으므로, 크로스 사이트 스크립팅(XSS) 공격에 취약할 수 있기 때문입니다.

CSRF 공격

  • 사용자의 권한을 도용하여, 사용자의 의도와 무관한 요청을 수행하게 만드는 공격입니다.
  • 악성 스크립트가 아니라 일반 버튼을 위변조하여 만들어도 쿠키는 쉽게 얻을 수 있습니다.
  • 공격자는 사용자가 로그인한 웹사이트의 쿠키를 이용하여, 위조된 요청을 전송합니다.
  • 공격자는 직접 사용자의 데이터에 접근할 수 없지만, 사용자의 권한으로 원하는 동작을 수행할 수 있습니다.
  • 주로 상태 변경 요청(글 작성, 삭제, 송금 등)을 목표로 합니다.

XSS 공격

  • 악성 스크립트를 웹 페이지에 주입하여, 사용자의 브라우저에서 원치 않는 동작을 수행하게 만드는 공격입니다.
  • 공격자는 웹 페이지에 악성 스크립트를 삽입하고, 사용자가 해당 페이지를 열면 스크립트가 실행됩니다.
  • 공격자는 사용자의 쿠키, 세션, 개인 정보 등을 탈취할 수 있습니다.
  • 주로 사용자의 데이터 탈취나 권한 상승 등을 목표로 합니다.

예시

해킹대상 페이지
9장 상태 저장9.2 서버 상태 저장