쿠키, 세션, JWT토큰

워니·2025년 1월 10일
0

컴퓨터네트워크

목록 보기
8/15

쿠키

  • 쿠키는 HTTP의 stateless한 특성을 보완하기 위한 대표적 수단으로, 서버에서 생성되어 클라이언트 측에 저장되는 <이름, 값>형태의 데이터이다
  • 사용자 인증이 유효한 시간을 명시할 수 있으며, 유효 시간이 정해지면 브라우저가 종료되어도 인증이 유지된다는 특징이 있다
  • 클라이언트의 상태 정보를 로컬에 저장했다가 참조한다
  • Response Header에 Set-Cookie 속성을 사용하면 클라이언트에 쿠키를 만들 수 있다
  • 쿠키는 사용자가 따로 요청하지 않아도 브라우저가 Request시 Request Header를 넣어 자동으로 서버에 전송한다
  • 개발자 탭의 application-storage-Cookies에서 확인 가능하다

쿠키의 사용 목적

HTTP 프로토콜의 특성이자 약점을 보완하기 위해 쿠키 또는 세션을 사용한다
기본적으로 HTTP프로토콜 환경은 stateless한 특성을 가지기 때문에 서버는 매 연결이 독립적으로 이루어진다.
하지만 로그인과 같은 경우, 상태 유지가 필수적이기 때문에 서버에 전송할 때 쿠키를 같이 전송하므로써 클라이언트가 누구인지 확인할 수 있다.

쿠키의 구성 요소

  • 이름 : 각각의 쿠키를 구별하는 데 사용되는 이름
  • 값 : 쿠키의 이름과 관련된 값
  • 유효시간 : 쿠키의 유지시간
  • 도메인 : 쿠키를 전송할 도메인
  • 경로 : 쿠키를 전송할 요청 경로

쿠키의 동작 방식

  1. 클라이언트가 페이지를 요청
  2. 서버에서 쿠키를 생성
  3. HTTP헤더에 쿠키를 포함시켜 응답
  4. 브라우저가 종료되어도 쿠키 만료시간이 있다면 클라이언트에 보관하고 있음
  5. 같은 요청을 할 경우 HTTP헤더에 쿠키를 함께 보냄
  6. 서버에서 쿠키를 읽어 이전 상태 정보를 변경할 필요가 있을때 쿠키를 업데이트하여 변경된 쿠키를 HTTP헤더에 포함시켜 응답

세션

세션이란

  • 세션은 쿠키를 기반으로 하고 있지만, 사용자 정보 파일을 브라우저에 저장하는 쿠키와 달리 세션은 서버 측에서 관리한다
  • 서버에서는 클라이언트를 구분하기 위해 세션ID를 부여하며 웹 브라우저가 서버에 접속해 브라우저를 종료할 때까지 인증상태를 유지한다
  • 사용자에 대한 정보를 서버에 두기 때문에 쿠키보다 보안에 좋지만, 사용자가 많아질수록 메모리를 많이 차지하게 된다
  • 클라이언트가 Request를 보내면, 해당 서버의 엔진이 클라이언트에게 유일한 ID를 부여하는데 이것이 세션ID이다

세션의 동작 방식

  1. 클라이언트가 서버에 접속하면, 서버는 해당 클라이언트를 식별하기 위해 세션 ID를 생성한다. 이 세션 ID는 서버가 관리하는 메모리(혹은 세션 저장소)에 저장되고, 클라이언트와 연결된다.
  2. 서버는 생성한 세션 ID를 클라이언트에게 응답할 때 쿠키에 담아서 보낸다. (예: Set-Cookie: JSESSIONID=abc123)
  3. 클라이언트는 쿠키에 저장된 세션 ID를 가지고 있다. 이후 클라이언트가 다시 요청을 보낼 때, 해당 세션 ID가 쿠키에 포함되어 자동으로 서버로 전송된다.
  4. 서버는 요청을 받을 때, 전송된 쿠키에서 세션 ID를 확인합니다. 그런 다음, 세션 저장소에서 이 세션 ID에 해당하는 데이터를 조회하여 클라이언트 정보를 가져온다.
  5. 서버는 조회한 세션 정보를 기반으로 요청을 처리하고, 클라이언트에게 응답한다.

쿠키와 세션의 차이

  • 쿠키와 세션은 비슷한 역할을 하며 동작원리도 비슷하다. 그 이유는 결국 세션도 쿠키를 사용하기 때문이다
  • 가장 큰 차이점은 사용자의 정보가 저장되는 위치이다. 쿠키는 서버의 자원을 전혀 사용하지 않으며, 세션은 서버의 자원을 사용한다.
  • 보안 면에서 세션이 더 우수하며, 요청 속도는 쿠키가 더 빠르다. 그 이유는 세션은 서버의 처리가 필요하기 때문이다.
  • 보안, 쿠키는 클라이언트 로컬에 저장되기 때문에 변질되거나 request에서 스니핑 당할 우려가 있어 보안에 취약하지만 세션은 쿠키를 이용해 sessionId만 저장하고 그것으로 구분해서 서버에서 처리하기 때문에 비교적 보안성이 좋다.
  • 라이플 사이클: 쿠키도 만료시간이 있지만 파일로 저장되기 때문에 브라우저를 종료해도 계속 정보가 남아있을 수 있다. 반면 세션도 만료시간을 정할 수 있지만 브라우저가 종료되면 만료시간에 상관없이 삭제된다

JWT 토큰(JSON Web Token)

JWT은 JSON객체에 인증이 필요한 정보들을 담은 후 비밀키로 서명한 토큰으로, 인터넷 표준 인증 방식이다. 공식적으로 인증(Authentication) & 인가(Authorization) 방식으로 사용된다.

JWT 프로세스

(로그인 전)
1. 사용자가 아이디와 비밀번호 혹은 소셜 로그인을 이용해 서버에 로그인 요청을 보낸다.
2. 서버는 비밀키를 사용해 JSON객체를 암호화한 JWT 토큰을 발급한다.
3. JWT를 헤더에 담아 클라이언트에게 보낸다
(로그인 후)
1. 클라이언트는 JWT를 로컬에 저장해놓는다
2. API 호출을 할 때마다 header에 JWT를 실어 보낸다
3. 서버는 헤더를 매번 확인하여 사용자가 신뢰할만한지 체크하고, 인증이 되면 API에 대한 응답을 보낸다.

JWT의 구조

JWT는 Header, Payload, signiture 3개로 구성되어있다.

  1. Header : 알고리즘, 토큰 타입
  2. Payload : 사용자 정보의 한 조각인 클레임(claim)이 들어있다
  3. signiture : 헤더와 페이로드의 문자열을 합친 후에, 헤더에서 선언한 알고리즘과 key를 이용해 암호화한 값이다.

header와 payload는 단순히 Base64로 인코딩되어 있어 누구나 쉽게 복호화할 수 있지만, signiture는 key가 없으면 복호화할 수 없다. 이를 통해 ##보안상 안전하다##는 특성을 가질 수 있게 되었다.

JWT의 장단점

1. 장점

  • 로컬에 저장하기 때문에 서버 용량에 영향을 끼치거나 받지 않는다
  • 보다 안전하다(공개키/개인키 or 비밀키를 통해 서명하기 때문에)
  • 모바일 앱에서 사용하기 적합하다(모바일 앱은 여러 플랫폼 및 기기에서 동작할 수 있고, 서로 다른 도메인에서 통신할 수도 있다. 이때 JWT를 사용하면 플랫폼 독립적으로 사용자 인증을 처리할 수 있기 때문에 적합하다)
  • 네트워크 부하가 적다(http헤더나 url파라미터를 통해 간단히 전송되기 때문)

2. 단점

  • 토큰의 크기가 커질수록 트래픽에 영향을 미칠 수 있다.
  • 토큰은 발급되면 만료 기간 변경이 불가능하므로 토큰 만료 처리를 구현해야 한다.

SOP

SOP(Same-Origin policy; 동일 출처 정책)은 자바스크립트 엔진 표준의 보안 규칙으로 하나의 출처에서 로드된 자원(문서나 스크립트)이 호스트나 프로토콜, 포트번호가 일치하지 않는 자원과 상호작용하지 못하도록 요청 발생을 제한하고 동일 출처(same origin)에서만 접근이 가능하도록 하는 정책

동일 출처

동일 출처란 두 URL의 프로토콜, 포트, 호스트가 모두 같은 출처이다
예를 들어 http://store.company.com/dir/page.html와 동일 출처인 것은 아래와 같다

교차 삽입의 경우 SOP 영향 X

아래와 같은 교차 삽입의 경우 SOP에 상관없이 허용된다

  • <link>태그로 다른 도메인의 CSS 가져오기
  • <script>태그로 다른 도메인의 Javascript 가져오기(단, 특정 ip에서는 차단될 수 있음)
  • <img>,<video>,<audio>,<embed>,<object>
  • <iframe>으로 삽입되는 모든 것
  • CSS, @font-face

교차 읽기(JS로 접근한 경우)는 허용되지 않는다\

IE의 경우에 SOP에 두 가지 중요한 예외사항이 있다

  • 신뢰가능한 사이트: 양쪽 도메인 모두가 높음 단계의 보안 수준을 가지고 있는 경우 동일 출처 제약을 적용하지 않는다.
  • 포트 무시 : 포트가 다르더라도 동일 출처로 여김

CORS

교차 출처 리소스 공유(Cross-Origin Resource Sharing, CORS)는 추가 HTTP 헤더를 사용하여, 한 출처에서 실행 중인 웹 애플리케이션이 다른 출처의 선택한 자원에 접근할 수 있는 권한을 부여하도록 브라우저에 알려주는 체제이다. 웹 애플리케이션은 리소스가 자신의 출처(도메인, 프로토콜, 포트)와 다를 때 교차 출처 HTTP 요청을 실행한다.

쉽게 설명하자면 CORS란 도메인이 다른 서버끼리 리소스를 주고 받는 정책이라고 생각하면 된다. 이게 왜 문제가 되냐면, 기본적으로 웹 브라우저의 기본 정책은 Same-Origin으로, origin이 다른 서버와의 리소스 공유를 허용하고 있지 않기 때문이다.

여기서 origin이란 scheme(일반적으로 프로토콜), host(도메인), port를 모두 포함하는 것으로, 셋 중 하나라도 일치하지 않는다면 다른 origin으로 판단한다. 예를 들어, 한 컴퓨터에서 React 서버(3000 포트)와 Springboot(8080 포트) 서버를 모두 띄워서 서로 리소스를 주고 받으려 한다면 포트가 다르기 때문에 origin이 달라 CORS 위반 문제가 발생하고, 개발자 도구 창에서는 시뻘건 CORS 위반 에러 메시지를 볼 수 있게 된다.

출처를 비교하는 로직은 서버가 아닌 브라우저에 구현되어 있다.
따라서, SOP를 위반하는 리소스 요청을 해도 서버에서는 정상 응답을 한 로그로 남을 수 있다

SOP를 위반하는 경우 브라우저는 요청의 헤더에 Origin, referer를 추가한다

  • Origin : 요청이 시작된 서버를 나타내는 url
  • referer : 현재 요청된 페이지의 링크 이전의 웹페이지 주소

CORS 사용 예시

다음과 같은 요청에 CORS가 사용된다

  • XMLHttpRequest, Fetch 호출
  • CSS, @font-face
  • webGL 텍스처
  • drawImage()를 사용해 캔버스에 그린 이미지/비디오 프레임
  • 이미지로부터 추출하는 CSS Shapes

    JavaScript, CSS같은 리소스들은 Cross-Origin 정책을 따르기 때문에 외부 요청이 가능하다

CORS는 어떻게 작동하는가

Preflight Request

기본적으로 브라우저는 HTTP 요청을 보낼 때, 사전에 OPTIONS 메서드를 통한 HTTP 요청을 보내서, 본 요청을 보내기 전에 안전한지 확인한다. 이를 preflight request라고 한다.

이 때 요청하려는 메서드 정보를 Access-Control-Request-Method 헤더에, 요청에 담길 헤더 정보를 Access-Control-Request-Headers 헤더에 담는다.

OPTIONS /resources/post-here/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:71.0) Gecko/20100101 Firefox/71.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Connection: keep-alive
Origin: http://foo.example
Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-PINGOTHER, Content-Type

그럼 서버는 응답으로 어떤 것들을 허용하는지에 대한 정보를 담아서 돌려준다

HTTP/1.1 204 No Content
Date: Mon, 01 Dec 2008 01:15:39 GMT
Server: Apache/2
Access-Control-Allow-Origin: https://foo.example
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
Access-Control-Max-Age: 86400
Vary: Accept-Encoding, Origin
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive

이런 식으로 Access-Control-Allow-Origin에는 허용된 origin 정보를,
Access-Control-Allow-Methods에는 허용하는 메서드를,
Access-Control-Allow-Header에는 사용 가능한 헤드 목록을,
Access-Control-Max-Age에는 현재의 preflight request를 브라우저가 캐싱 가능한 최대 시간을 담아서 제공한다

Simple Request

하지만 이런 preflight request를 보내지 않는 경우가 있는데, 대표적인 경우 브라우저를 사용하지 않는 경우(ex-postman 사용)가 있는데, 이를 simple request라고 한다. simple request는 다음의 세 가지 조건을 만족해야 한다

  • 본 요청 메서드가 GET, HEAD, POST 중 하나일 것
  • 클라이언트에서 자동으로 넣어주는 헤더와 Fetch 표준 정책에서 정의한 CORS-safelisted request header라는 헤더 목록에 들어 있는 헤더 외에 다른 헤더를 수동으로 넣어주지 않았을 것
  • CORS-safelist request Header: Accept, Accept-Language, Content-Language, Content-Type
  • Content-Type의 경우 다음의 값들만 있을 것: application/x-www-form-urlencoded, multipart/form-data, text/plain

이 경우에는 preflight 요청을 보내지 않고 서버가 본 형태에 대한 응답 헤더에 CORS관련된 헤더를 보내서 브라우저가 이를 검사하는 형태로 CORS 위반 정책 여부를 검사한다

Credentialed Request

이외에 헤더에 인증과 관련된 정보를 담아서 보내는 credentialed request라는 경우도 있는데, 이 경우에는 credentials 옵션을 사용하며 CORS 정책 위반 여부를 검사하는 규칙에 몇 가지 규칙이 더 들어가게 된다.

자바스크립트의 fetch API를 사용하거나 Axios, Ajax 등을 사용할 때 서버로 쿠키를 함께 전송해야 하는 경우가 있는데, 요청에 쿠키가 담기게 되면 Credentialed Request 허용이 되어 있어야 한다. 이 때는 서버 쪽에서 응답 헤더에 Access-Control-Allow-Credentials: true를 보내주지 않는다면 브라우저에서 응답을 받는 것을 거부하게 된다.

여기서 주의할 점이 있다. Credentialed Request의 경우, Access-Control-Allow-Origin 헤더 값이 와일드카드여서는 안된다. 대신 https://foo.com과 같이 구체적인 origin을 지정해주어야 한다.

서버에서 CORS 허용해주기

WebMvcConfigurer를 상속받은 @Configuration 빈을 만들어 주면 된다

@Configuration
public class WebConfig implements WebMvcConfigurer {
    public static final String ALLOWED_METHOD_NAMES = "GET,HEAD,POST,PUT,DELETE,TRACE,OPTIONS,PATCH";

    @Override
    public void addCorsMappings(final CorsRegistry registry) {
        registry.addMapping("/api/**")
                .allowedMethods(ALLOWED_METHOD_NAMES.split(","))
                .exposedHeaders(HttpHeaders.LOCATION);
    }
}

WebMvcConfigurer에는 addCorsMappings라는 메서드가 존재하는데, 이 메서드로 CORS정책을 허용해 줄 url을 지정해 줄 수 있다.
origin을 지정해주지 않는다면 기본 값으로 '*'를 지정해서 모든 origin에 대해 CORS를 허용한다


출처 : https://velog.io/@wogkr1383/SOP%EC%99%80-CORS,
https://velog.io/@ohzzi/CORS-%ED%97%88%EC%9A%A9-%EC%A2%80-%ED%95%B4%EC%A3%BC%EC%84%B8%EC%9A%94

profile
매일, 조금씩 나아가는중

0개의 댓글