Authentication, Authorization (JWT, session, OAuth)

newhyork·2022년 4월 30일
0

Authentication(인증)


  • 최초 로그인 후, 로그인 한 상태임을 나타내는 것.
  • 클라이언트 측에서 로그인한 유저가 사용할 수 있는 기능에 대한 request 시,
    매번 ID/PW를 보내면서 인증하는 것은 보안 상 좋지 않고
    서버 측에서도 계속 DB를 확인하여 검증해야 하므로 cost가 많이 든다.
  • 따라서 최초 로그인 이후의 로그인 상태임을, 인증으로 대체한다.
  • JWT는 access token, session은 session ID를 각각 인증으로 사용하는데,
    최초 로그인 성공 시 서버의 response set-cookie로 클라이언트의 브라우저 쿠키에 저장하여
    이후 클라이언트의 request 시 자동으로 전송되곤 한다.

Authorization(인가)


  • 로그인한 유저의 권한에 대한 것.
    즉 인증이 이루어진 후, resource에 대한 유저의 접근 권한 등에 대한 것이다.
  • 권한 정보는 보통 JWT는 accesst token의 payload에 넣는 방식으로 구현하고,
    session은 서버 내에 저장하는 방식으로 구현한다.

쿠키


  • HTTP의 stateless를 보완하기 위해 사용한다.
  • 브라우저에 저장되어 있으면 클라이언트가 request 시 자동으로 전달되는 헤더이다.
    따라서 JWT나 session ID를 쿠키에 저장하여 사용하곤 한다.
    이 때, 브라우저에 안전하게 저장하기 위해 해당 쿠키는 httponly옵션을 갖도록 하곤 한다.
  • 쿠키는 expire 값에 따라 두 종류의 쿠키로 나뉜다.
    세션 쿠키는 session으로 되어있고, 브라우저를 완전히 껐을 때 삭제된다.
    지속 쿠키는 날짜가 설정되어있고, 만료되거나 삭제하지 않는 한 유지된다.

JWT


JSON Web Token

  • JSON 포맷을 이용하여 사용자에 대한 정보를 저장하는 claim 기반의 웹 토큰.
  • header, payload, signature로 구성되어 있다.
    각각은 원래 JSON 형태이나,
    Base64로 인코딩되어 ‘.’을 구분자로 하는 하나의 문자열 값을 가진다.
    (참고로 Base64는 암호화가 아니다)
    • header: 토큰의 타입, 해싱 알고리즘을 지정한다.
    • payload: 3가지 종류의 claim으로 나뉜 데이터들을 담고 있다.
      • 발급자, 발급 시간, 고유 식별자 등의 정보가 담기곤 하며, 커스텀하기 나름이다.
      • 여기에 비밀번호 같은 민감한 것들을 넣어선 안된다.
        주로, 권한을 비롯하여 유저를 식별하기 위한 정보가 담긴다.
    • signature: 서버의 secret key(JWT)를 통해 header와 payload를 암호화한 것으로,
      서버에서는 이를 통해 토큰의 진위 여부를 판단할 수 있다.
      • 좀 더 정확히 말하자면, header와 payload를 각각 Base64로 인코딩한 값을 합친 후,
        서버의 secret key를 통해 암호화(해싱)한 결과가 signature와 같다면,
        해당 서버에서 발급한 토큰이 맞다고 판단한다.
      • 비밀번호 저장 시와 마찬가지로 단방향 암호화이므로, 복호화는 불가능하다.
  • refresh token 저장과 관련한 것을 제외하곤
    인증 및 인가에 관한 resource를 서버에서 부담하지 않으므로 경제적이다.
  • QR 체크인 등에 쓰인다.
  • REST API 서버라면 stateless에 따라, session보다 JWT가 적절하다.

JWT flow


(인증은 어디까지나 구현하기 나름이다. 여기서 소개하는 건 하나의 예시이다)

  1. 클라이언트가 로그인에 성공한다면 서버는 access token 및 refresh token을 발급한다.
    서버는 클라이언트에게 set-cookie로 access token와 refresh token을 넘겨준다.
    access token에는 유저의 정보(권한 등)가 저장되는 토큰으로,
    추후 payload부분이 decode되어 활용된다.
    refresh token은 access token의 재발급 및 보안을 위해 쓰인다.
    (refresh token에 정보를 저장할지/말지, 어떤 정보를 넣을 것인지,
    클라이언트에 넘겨줄지 말지, 서버에서도 저장을 할지/말지 등은 구현하기 나름이다.
    서비스에 맞게 선택하면 된다.)
  2. 클라이언트는 로그인이 필요한 기능 등을 이용하기 위해 서버에 요청 시,
    서버로부터 발급 받은 access token을 쿠키에 담아 전송하여 인증을 시도한다.
  3. 서버는 access token의 header와 payload 정보를 자신의 secret key로 암호화해본다.
    암호화 결과값과 해당 access token의 signature를 비교하여 일치하면,
    해당 서버에서 발급한 제대로 된 access token이므로 클라이언트의 인증이 성공한다.
  • 서버에서는 access token의 유효 기간을 검사하여 만료된 토큰이 아닌지 확인하며,
    만료되었다면 로직에 따라 refresh token을 통해 access token을 발급한다.
  • 토큰의 진위 여부 검증 시에는 signature를 해독이 아니라 비교하는 것이고,
    사용할 때는 token의 payload를 decode한 것을 사용한다.

보안


  • XSS 공격은 해커가 브라우저에 일반 쿠키나 storage에 저장된 정보를 탈취하는 스크립트를 심는 것이다.이러한 위험으로부터 안전하게 보호하고자, 쿠키에 httponly옵션을 두어 response로 넘겨줘야한다.
  • CSRF 공격은 해커가 사용자의 정보를 가지고 서버에 요청을 보내는 스크립트를 심는 것이다.
    httponly 쿠키를 사용함에 따라 CSRF 공격에 방어하기 위해서
    서버에서는 CSRF token을 발급하는 방식을 사용하기도 한다.
    클라이언트 측에서는 access token과 함께 CSRF token을 헤더에 추가하여 request한다.
    서버가 CSRF token을 요구하는데 CSRF token없이 request하면
    "Missing CSRF token"이 응답 되고, access token만으로는 불가능해진다.

refresh token


  • refresh token없이 단순히 access token의 유효 기간을 길게 한다면
    탈취당했을 시 긴 시간 동안 악용될 우려가 있기 때문에
    이를 방지하고자 access token의 유효 기간은 짧게 하고, refresh token을 따로 둔다.
  • 단순히 access token을 재발급 하기 위함이 아니라 만약 access token이 탈취당했을 때,
    이를 재발급하는 refresh token을 파기함으로써 더 이상 재발급 하지 못하게 하고
    탈취된 access token을 짧은 유효 기간만 사용할 수 있게 하기 위함이 더 정확한 목적이다.
  • token의 탈취 여부를 알 수 있는 방법은,
    사용자의 신고나 서버의 검증 로직에 의해 알 수 있다.
    서버가 검증을 한다는 건, 그 만큼 서버에서 그에 대한 resource를 들인다는 말이다.
    서버의 검증 로직에는 다양한 방법이 있을 수 있다.
    동일 IP에서 인증을 시도한 게 맞는지 검증하거나,
    access token이 만료되지 않았는데 재발급을 시도하거나 등이다.
    혹은 access token 및 refresh token을 DB에 저장해 놓고,
    인증을 시도할 때마다 검증하는 방법도 있다.
  • 만약 해커가 refresh token을 탈취했다면 access token을 재발급 하려 할 것이다.
    이에도 대비할 수 있도록 서버에서는 검증 로직을 두어, 해당 refresh token을 파기해야 한다.
  • 서버 측에서 Redis와 같은 DB에 refresh token을 캐싱하는 방식으로 구현하는 방법이 있다.
    • Redis는 휘발성 DB로, 기간에 따라 데이터가 사라지는 특징을 갖는다.
    1. 서버 측에서 access token과 refresh token을 생성 시,
      access token과 1:1 대응시켜 refresh token을 저장해둔다.
    2. 이후, 클라이언트에서 access token이 만료된 요청을 보낼 시
      서버 측에서는 그 만료된 access token의 refresh token을 통해
      새로운 access token을 발급하여 클라이언트에게 넘겨준다.
    3. 서버 측에서도 만료된 access token 대신 새로 발급한 것을 다시 저장한다.
      (이 또한, 어디까지나 구현하기 나름이다)

session


  • JWT와 달리, 서버에서 인증 및 인가에 대한 resource를 부담한다.
    • 서버 측에 파일 등의 형태로 하드디스크, 메모리 혹은 DB에 저장된다.
  • 서버에서 사용자를 제어할 수 있다.
    • 한 기기에서만 로그인이 가능하게 하려는 경우
    • 계정 공유 숫자를 제한하려는 경우

session flow


(인증은 어디까지나 구현하기 나름이다. 여기서 소개하는 건 하나의 예시이다)

  1. 로그인 성공 시, 서버 측에서는 secret key를 통해 session ID를 발급하며
    session ID를 key로, JWT payload와 같은 유저 정보를 value로 하는 쌍을 서버 측에 저장한다.
  2. 서버에서는 set-cookie를 통해 클라이언트의 브라우저 쿠키에 session ID를 저장하고,
    클라이언트는 요청 시 session ID를 통해 인증한다.

OAuth (2.0)


  • 어떤 서비스가 소셜 로그인을 통한 로그인 시 사용하는 인증 및 인가 표준 프로토콜이다.
  • 해당 서비스가 OAuth 제공자의 API를 이용할 필요가 있을 때 용이하다.

OAuth flow


(이 또한 플랫폼마다 구현된 방식이 다르다.
카카오 OAuth는 authorization code grant 방식이며 이를 기준으로 알아보자.)

  1. 클라이언트가 서비스 서버에게 소셜 로그인을 할 거라는 요청을 보낸다.
  2. 서비스 서버가 인증 서버에 OAuth 절차를 밟기 위한, authorization code 요청을 한다.
    (query parameter는 해당 인증 서버의 API를 사용할 수 있는 자신의 REST API 키인 client id와 인가 코드를 받았을 때 redirect할 API의 URL, 인가와 관련된 동의 항목 설정 등으로 구성된다.
    이 정보들은 3~6 과정 동안 계속 주고 받게 된다.)
  3. 인증 서버가 클라이언트에게 로그인 창을 띄워준다.
  4. 클라이언트는 '카카오'에 로그인 한다.
  5. 인증 서버는 클라이언트에게 각종 정보 제공 동의를 받는 창을 띄운다.
    이는 인가에 대한 내용으로, 서비스 제공자가 access token 등으로 카카오 API에 요청 시 취득할 수 있는 사용자의 정보에 대한 권한을 ‘사용자’가 동의/거부하는 것이다.
    즉, 서비스 제공자가 유저에게 서비스하기 위해 필요한 유저 정보에 접근하는 권한들에 대한 것이며, 2에서 미리 설정한 동의 항목으로 구성된다.
  6. 클라이언트는 정보 제공에 동의한다.
  7. 인증 서버는 서비스 서버에게 redirect URL로 authorization code(인가 코드)를 response한다.

여기까지, 서비스 서버가 인가 코드를 받기 위해 request한 것에 따른 인증 서버의 response이다.

  1. 서비스 서버는 인가 코드로 인증 서버에 token 발급을 요청한다. (redirect)
  2. 인증 서버가 서비스 서버에 access token, refresh token (id token은 선택)을 보내준다.
    (access token에는 사용자의 정보에 대한 서비스 제공자의 접근 권한이 담겨있다)

여기까지, 서비스 서버가 토큰을 받기 위해 request한 것에 따른 인증 서버의 response이다.
(지금까지가 '카카오' 로그인에 대한 것이고 이젠 '서비스' 로그인이므로, 구현하기 나름이다.)

  1. 서비스 서버에서는 access token을 통해 '사용자 정보 가져오기'와 같은 카카오의 API를 이용하고, 카카오 API 서버에서는 access token의 권한 정보에 따라 사용자의 정보를 넘겨주어,
    서비스 서버에서는 해당 정보들을 회원가입 및 로그인에 사용하게 된다.
    사용자가 자기 서비스의 회원인지/아닌지 id를 통해 확인하여 로그인/회원가입 절차를 밟는다.
    (id는 카카오 측의 사용자 식별자로, 위처럼 access token을 통해 얻거나 id token 자체에 있다. )
  • 이 이후에도 카카오가 제공하는 API를 사용하고자 할 때는 access token을 request에 사용한다.
  • 클라이언트에 유효한 카카오 계정 세션이 있는 상태라면 3, 4를 생략하고 2→5이다.
    5, 6은 사용자가 서비스와 카카오를 최초 연결 시에만 나타나며, 이후는 4→7이다.
    따라서, 클라이언트에 유효한 세션이 있고 최초 연결 이후라면 2→7이고,
    서비스에 회원 가입을 했다면 사용자는 1 하나만으로 로그인을 하게 된다.
  • OAuth를 통한 로그아웃은 단순히 서비스 서버에서의 로그아웃이 아니라,
    서비스 서버를 통해 인증 서버에 로그아웃 API를 요청해서
    인증 서버에서의 로그아웃을 하는 것이다.
    해당 서비스와 관련된 토큰을 만료 시킴으로써,
    해당 사용자의 정보로 카카오 API를 요청할 수 없게 해야한다.

0개의 댓글