[Backend] 인증 / 보안 (1) - Cookie, Session, Token

선정·2022년 7월 13일
0

Today I Learned

  • Cookie
  • Session
  • Token

Cookie

서버가 어떤 데이터를 브라우저 측에 저장한 후 다시 그 데이터를 받아오는 기술, 또는 그 데이터 자체

해당 도메인에 대해 쿠키가 존재하면 웹 브라우저는 http 요청 시, 서버에 쿠키를 함께 전달한다. 이러한 쿠키를 활용하면, HTTP 프로토콜은 상태가 없는(stateless) 특성을 가지고 있음에도 정보를 유지할 수 있다.

서버가 브라우저로 쿠키를 보내는 것은 일회성 작업이지만, 브라우저가 서버로 쿠키를 돌려 보내는 것은 일정 시간동안 반복해서 수행하는 작업이다. 즉, 서버에서는 쿠키를 한번 보내서 브라우저에 저장해두면, 브라우저가 일정 기간동안 알아서 쿠키를 서버로 보내주는 것이다.

클라이언트 서버 모델에서는 서버가 클라이언트의 요청없이 클라이언트로 데이터를 보낼 수 없다. 따라서 쿠키 전달 과정은 서버가 클라이언트 요청에 응답할 때 일어난다. http 응답 시, 서버는 Set-Cookie라는 응답 헤더에 브라우저가 수신해야 할 쿠키 정보를 명시한다.


http 응답 헤더의 쿠키 옵션

'Set-Cookie':[
            '이름1=값1',
            '이름2=값2; Secure',
            '이름3=값3; HttpOnly',
            '이름4=값4; Path=/cookie',
            '이름5=값4; Domain=google.com'
        ]
'Set-Cookie':[
            'cookie=iamcookie', 
            'Secure=Secure; Secure',
            'HttpOnly=HttpOnly; HttpOnly',
            'Path=Path; Path=/cookie',
            'Doamin=Domain; Domain=google.com'
        ]

http 요청 헤더의 쿠키 옵션

  • 서버로부터 쿠키를 응답 받은 브라우저는 해당 쿠키를 클라이언트 컴퓨터의 하드디스크에 저장한다.
  • 이후, 브라우저가 동일한 서버에 요청을 할 때 저장해놓은 쿠키를 Cookie라는 요청 헤더에 실어서 돌려 보낸다.

쿠키 옵션 종류

1. Domain

쿠키 옵션에서 도메인은 포트 및 서브 도메인 정보, 세부 경로를 포함하지 않는다. 예를 들어 요청해야 할 URL가 https://www.google.com/users/login 이라면 Domain은 google.com이 된다. 이를 통해 google.com이 아닌 도메인에 쿠키를 전송하는 일을 막을 수 있다.


2. Path

URL의 세부 경로로, 요청해야 할 URL가 https://www.google.com/users/login 이라면 Path는 /users/login이 된다. Path는 설정된 경로를 포함하는 하위 경로로 요청을 하더라도 쿠키를 서버에 전송할 수 있다. 예를 들어, Path가 /users로 설정돼있으면 /users/signup 경로로 쿠키를 전송할 수 있다.
따로 명시하지 않는 경우, Path의 기본 값은 /이다.


3. MaxAge or Expires

쿠키가 유효한 기간을 정하는 옵션이다. 유효기간을 설정하는 것이 보안상 유리하다.

  • MaxAge : 쿠키가 유효한 시간을 초 단위로 설정
  • Expires : 언제까지 쿠키가 유효한지 설정

위 옵션의 여부에 따라 쿠키는 세션 쿠키(Session Cookie)영속성 쿠키(Persistent Cookie)로 나눠진다.

  • 세션 쿠키(Session Cookie) : MaxAge 또는 Expires 옵션이 없는 쿠키로, 브라우저를 종료하면 해당 쿠키는 삭제됨
  • 영속성 쿠키(Persistent Cookie) : 브라우저의 종료 여부와 상관없이 MaxAge 또는 Expires에 지정된 유효시간만큼 사용가능함

4. Secure

사용하는 프로토콜에 따른 쿠키의 전송 여부를 결정하는 옵션이다.

  • Secure을 명시한 경우 : https를 이용하는 경우에만 쿠키 전송 가능
  • Secure을 명시하지 않은 경우 : http, https 모두에 쿠키 전송 가능

단, Secure 옵션을 명시했지만 도메인이 localhost인 경우에는 https가 아니여도 쿠키 전송이 가능하다.


5. HttpOnly

자바스크립트로 브라우저의 쿠키에 접근이 가능한지 여부를 결정하는 옵션이다.

  • httpOnly을 명시한 경우 : 브라우저에서 자바스크립트로 쿠키에 접근할 수 없음
  • httpOnly을 명시하지 않은 경우 : 자바스크립트로 Document.cookie 객체를 통해 쿠키에 접근할 수 있어 쿠키 탈취의 위험이 있음

6. SameSite

Cross-Origin(CORS) 요청을 받은 경우, 요청에서 사용한 메소드와 해당 옵션의 조합을 기준으로 서버의 쿠키 전송 여부를 결정한다.

Cross-Origin : 도메인, 프로토콜, 포트 중 하나라도 다른 경우

  • Lax : Cross-Origin 요청이라면 GET 메소드에 대해서만 쿠키를 전송 가능
  • Strict : Cross-Origin이 아닌 same-site 인 경우에만 쿠키를 전송 가능
  • None : Cross-Origin이더라도 항상 쿠키 전송 가능하나, 대신 Secure 옵션이 필요함


Session

세션기반 인증 (Session-based Authentication)

  1. 유저가 로그인을 한다. (HTTP 요청)
  2. 서버에서 유저 정보를 확인한다.
  3. 유저의 고유한 ID 값을 부여해 세션 저장소에 저장한다.
  4. 그리고 이와 연결되는 Session ID를 발행한다.
  5. 서버는 해당 Session ID를 쿠키에 담아 클라이언트에게 응답을 보내고 클라이언트는 이를 저장한다.
  6. 클라이언트는 인증이 필요한 요청마다 저장된 쿠키를 헤더에 실어 보낸다.
  7. 서버에서는 쿠키를 받아 세션 저장소에서 대조를 한 후 대응되는 정보를 가져온다.
  8. 인증이 완료되고 서버는 유저에 맞는 데이터를 보내준다.

로그아웃
클라이언트에서 세션 정보를 없애기 위해서는 res.cookie로 쿠키의 값을 무효한 값으로 갱신하거나, res.clearCookie로 쿠키를 삭제하면 된다.


장점

  1. 서버에서 인증 상태를 관리하기 때문에, 경우에 따라 강제 로그아웃 시킬 수 있다.
  2. 클라이언트에서 임의로 정보를 변경하더라도 서버에서 인증 상태를 관리하고 있기 때문에 상대적으로 안전하다.

단점

  1. 서버에 데이터를 저장하므로 세션 양이 많아질수록 서버에 부하가 커진다.
  2. 서버 확장을 해야할 때 세션을 관리하기가 어렵다. (서버는 공통된 세션 데이터를 가져야 함)

express-session

세션을 위한 미들웨어로, express 서버에서 쉽게 세션을 위한 공간을 다룰 수 있도록 만들어준다. secret 옵션의 비밀키를 이용해 암호화해 세션 id를 생성해 쿠키에 담아 클라이언트로 전송한다.

const express = require('express');
const session = require('express-session');

const app = express();

app.use(
  session({
    secret: '@secretKey',
    resave: false,
    saveUninitialized: true,
    cookie: {
      domain: 'localhost',
      path: '/',
      maxAge: 24 * 6 * 60 * 10000,
      sameSite: 'none',
      httpOnly: false,
      secure: true,
    },
  })
);


Token

해싱 (Hashing)

가장 많이 쓰이는 암호화 방식중에 하나로, 복호화가 가능한 다른 암호화 방식들과 달리 해싱은 암호화만 가능하다. 해싱의 목적은 데이터 그 자체를 사용하는 것이 아니라, 동일한 값의 데이터를 사용하고 있는지 여부만 확인하는 것이 목적이기 때문이다. (단방향 암호화 방식)

  • 해시 함수(Hash Function)의 특징
    • 항상 같은 길이의 문자열을 리턴한다.
    • 동일한 문자열 + 동일한 해시 함수 ➡️ 항상 같은 결과값
    • 서로 다른 문자열 + 동일한 해시 함수 ➡️ 항상 다른 결과값
    • e.g) 비밀번호를 DB에 저장할 때 해싱한 값으로 저장
    • 해시 함수 중 하나인 SHA1로 해싱 해보기

레인보우 테이블(Rainbow Table)

( 이미지 출처: https://ko.wikipedia.org/wiki/%EB%A0%88%EC%9D%B8%EB%B3%B4_%ED%85%8C%EC%9D%B4%EB%B8%94 )

해시 함수를 사용해 변환 가능한 모든 해시 값을 저장시켜 놓은 표로, 해시 함수를 거치기 이전의 값을 알아내는 데에 사용된다. 유출 될 경우, 보안의 위협이 될 수 있다. 이를 대비해 솔트를 활용한다.


솔트(Salt)

( 이미지 출처: https://ko.wikipedia.org/wiki/%EC%86%94%ED%8A%B8_(%EC%95%94%ED%98%B8%ED%95%99 )

해싱 이전 값에 임의의 값을 더해 데이터가 유출되더라도 해싱 이전의 값을 알아내기 더욱 어렵게 만드는 방법이다. 해싱 대상 값에 대해 복잡도를 높이거나, 같은 입력 값에 대해 다른 해시 값을 줄 수 있다.



JWT (JSON Web Token)

JSON 객체에 정보를 담고 이를 토큰으로 암호화하여 전송할 수 있는 기술


JWT의 구성


( 이미지 출처 : https://millo-l.github.io/JWT-%EA%B8%B0%EB%B0%98-%EC%9D%B8%EC%A6%9D%EB%B0%A9%EC%8B%9D/ )

1. Header
토큰의 타입과 해시 암호화 알고리즘을 JSON 형태로 작성한다.

2. Payload
전달하려는 내용물을 담고 있는 부분으로, 마찬가지로 JSON 형태로 작성한다.

3. Signature
토큰의 무결성을 확인할 수 있는 부분으로, 시크릿키와 Header의 알고리즘을 사용해 해싱한다.



토큰기반 인증 (Token-based Authentication)

Access Token
서버에 접근하기 위한 토큰으로, 일반적으로 암호화된 인증 정보를 담고있다. 보안을 위해 보통 24시간 정도의 짧은 유효기간이 설정된다.

Refresh Token
서버 접근을 위한 토큰이 아닌 액세스 토큰이 만료되었을 때 새로운 액세스 토큰을 발급받기 위해 사용되는 토큰이다. 그렇기 때문에 Access Token보다 유효기간이 길게 설정된다.


1. Access Token Only

  1. 유저가 로그인을 한다. (HTTP 요청)
  2. 서버에서 유저 정보를 확인하고 유저의 고유한 ID 값을 부여하고 다른 기타 정보들과 함께 Token에 담는다.
  3. 시크릿키를 이용해 암호화된 Access Token을 발행한다.
  4. 서버는 클라이언트에 해당 Access Token을 응답으로 전달하고 클라이언트는 이를 저장한다.
  5. 클라이언트는 인증이 필요한 요청마다 저장한 Access Token을 토큰을 헤더에 실어보낸다.
  6. 서버에서는 전달받은 Access Token의 Verify Signature를 시크릿키로 복호화하여 조작 여부, 유효기간을 확인한다.
  7. 검증이 완료되면 payload를 디코딩해서 유저의 ID 값에 맞는 데이터를 보내준다.

장점

  1. 서버에서는 인증 상태를 관리하지 않아 서버의 메모리, DB 등에 부담이 없다.
  2. 서버에서 세션을 관리하지 않기 때문에 서버를 확장하기 용이하다.
  3. 토큰 생성과 검증이 하나의 서버에서 이루어지지 않아도 되기 때문에 토큰 생성만을 담당하는 서버를 구축할 수 있다.
    ➡️ 여러 서비스에 공통된 인증 서버 구현 가능
  4. 토큰은 인증 상태, 접근 권한 등 다양한 정보를 담을 수 있기 때문에 사용자 권한 부여에 용이하다.

단점

  1. 서버에서 인증 상태를 관리하지 않아서, 토큰이 탈취돼도 해당 토큰을 강제로 만료시킬 수 없다.
  2. 보안상의 이유로 유효기간을 짧게 설정하면 잦은 로그인으로 좋지 못한 ux를 제공할 수 있다.
  3. 토큰에 많은 데이터를 담으면 그만큼 암호화하는 과정도 길어지고 토큰의 크기도 커지기 때문에 네트워크 비용 문제가 생길 수 있다.

2. Access Token + Refresh Token

  1. 유저가 로그인을 한다. (HTTP 요청)
  2. 서버에서 유저 정보를 확인하고 유저의 고유한 ID 값을 부여하고 다른 기타 정보들과 함께 Token에 담는다.
  3. 시크릿키를 이용해 암호화된 Access Token, Refresh Token을 발급한다. (이때, 일반적으로 회원 DB에 Refresh Token을 저장해둔다.)
  4. 서버는 클라이언트에 해당 Access Token과 Refresh Token을 응답으로 전달하고 클라이언트는 이를 저장한다.
  5. 클라이언트는 인증이 필요한 요청마다 저장한 Access Token을 토큰을 헤더에 실어보낸다.
  6. 서버에서는 전달받은 Access Token의 Verify Signature를 시크릿키로 복호화하여 조작 여부, 유효기간을 확인한다.
  7. 검증이 완료되면 payload를 디코딩해서 유저의 ID 값에 맞는 데이터를 보내준다.
  8. 지정한 유효기간이 지나 Access Token이 만료된다.
  9. 클라이언트는 이전처럼 인증이 필요한 요청을 보낼 때 (만료된) Access Token을 토큰을 헤더에 실어보낸다.
  10. 서버는 Access Token이 만료됨을 확인하고
  11. 클라이언트에 권한없음을 신호로 보낸다.
  12. 사용자는 Refresh Token과 Access Token을 함께 서버로 보낸다.
  13. 서버는 전달받은 Access Token의 조작 여부를 확인한 후, 전달받은 Refresh Token과 회원 DB에 저장되어 있던 Refresh Token을 비교한다. Token이 동일하고 유효기간도 지나지 않았다면 새로운 Access Token을 발급한다.
  14. 서버는 새로운 Access Token을 헤더에 실어 다시 API 요청을 진행한다.

Access Token 만료가 될 때마다 계속 과정 9~11을 거칠 필요는 없다.
클라이언트에서 Access Token의 Payload를 통해 유효기간을 알 수 있기 때문에 프론트엔드 단에서 API 요청 전에 토큰이 만료됐다면 바로 재발급 요청을 할 수도 있다.


장점

  1. 보안상의 이유로 Access Token 유효기간을 짧게 설정한 경우, Refresh Token의 기간이 남아있다면 재로그인 없이 Access Token을 재발급 받을 수 있다.

단점

  1. Refresh Token이 도난됐을 경우 계속해서 Access Token을 발급 받으며 악용될 가능성이 있다.
  2. Access Token만 사용하는 것보다 구현이 복잡하다.

참고

profile
starter

0개의 댓글