JWT 인증 방식

Tony·2021년 7월 15일
2

JWT : JSON WEB TOKEN


전자 서명 된 URL로 이용할 수 있는 문자만으로 구성된 JSON
전자 서명은 JSON의 변조를 체크 할 수 있음

인증방식 및 원리

JWT 구성

aaaa.bbbb.cccc 와 같이 세 파트로 나누어짐
각각 header.payload.signature 로 구성됨
Base64 인코딩의 경우 "+", "/", "=" 이 포함되지만 JWT는 URI에서 파라미터로 사용할 수 있도록 URL-Safe한 Base64url 인코딩을 사용(+, /, = 같은 문자가 다른 string으로 대체 되는 듯)

  • 토큰의 타입과 해시 암호화 알고리즘으로 구성
    • 토큰 유형(JWT)
    • 해시 알고리즘(HMAC, SHA256, RSA 등)
{
  "typ": "JWT",
  "alg": "HS256"
}

Payload

전달하려는 정보(ex. 유저 id, role 등) : claim 이라고도 부름

  • key : value 형태로 전달
{
  "iss": "//sso.tvcf.co.kr",
  "iat": 1626308465,
  "exp": 1626394865,
  "roles": [
    "guest",
    "user"
  ],
  "userId": "tonydev",
  "userName": "tony",
  "userType": 3,
  "userLevel": 1
}
일반토큰 vs claim
  • 일반 토큰의 문제점

    • 발급된 토큰에 대해 만료를 시킬 수단이 없다.
    • 발급된 토큰을 검사하거나 처리할 때 마다 DB에 접근하여 검사할 경우 부담이 생긴다.
    • 사용자 로그아웃 등으로 인한 토큰을 관리할 수 있는 방법이 없다.
  • 클레임(claim : 청구, 요구)

    • 정보를 담고 있는 토큰
    • 클레임의 종류 3가지
      1. 등록된 클레임(Registered Claim)
        • 토큰 정보를 표현하기 위해 이미 정해진 데이터 종류, 선택적으로 작성 가능
        • iss : 토큰 발급자(issuer)
        • sub : 토큰 제목(subject)
        • aud : 토큰 대상자 (audience)
        • exp : 토큰의 만료 시간(expiration)
        • nbf : 토큰 활성 날짜(not before) - 이 날짜가 지나기 전 토큰은 활성화 되지 않음
        • iat : 토큰이 발급된 시간(issued at)
        • jti : JWT Id - 중복 방지를 위해 사용, 일회용 토큰(Access token 등)에 사용
        • 시간 관련 숫자 : NumericData 확인 방법
          • new Date(parseInt("1626394865") * 1000)
      2. 공개 클레임(Public Claim)
        • 충돌을 방지하기 위해서 공개된 이름
        • URL 형태로 작성
        • 예시 : { "https://hexlant.com": true }
      3. 비공개 클레임(Public Claim)
        • 실제 사용을하는 개발자가 지정, 서버와 클라이언트가 서로 정의하여 사용하는 클레임
        • { "token_type": "access" }

signature

secret key를 포함하여 암호화 되어 있음

  • header에 지정된 알고리즘과 secret key, 서명으로 payload와 header를 담는다.

Access token과 Refresh token

Access token : JWT

Refresh token : access token을 얻기 위한 토큰

  • access 토큰을 사용하다가 만료되면 refresh 토큰으로 재발급
  • 평소 API요청엔 refresh token을 같이 보내지 않음
  • refresh 토큰은 어디에서 저장되고 언제 같이 보내지는 걸까?
    • 평소엔 access 토큰만 api서버로 같이 전달해서 인증하는데 refresh 토큰은 브라우저의 어디에 저장는건지 ?
    • access 토큰이 만료된 시점을 어떻게 알고 refresh 해주는 건지 어떻게 ?
    • refresh 토큰이 저장될 만한 곳
      1. Client side(browser)
        1-1. Local storage
        • 자바스크립트로 접근이 너무 쉬워서 XSS 공격에 취약
          1-2. Cookie
        • 쿠키의 경우 HTTPOnly와 Secure옵션을 사용하고 CSRF 공격에 대비를 하면 어느정도 보안이 됨
      2. Server side
        2-1. 세션
        • 세션에 저장하고 세션 만료주기를 길게 준다 : 사용자가 많아지면 말도 안되는 방식, 애초에 세션을 사용하지 않으려고 만든게 엑세스 토큰방식임
        2-2. DB에 저장
        • DB에 refresh 토큰을 저장하고 그 index값을 쿠키나 로컬스토리지에 저장(해쉬처리 하면 보안에 더 유리)

JWT 발행

header(JOSE 헤더)

new Buffer('{"alg":"HS256","typ":"JWT"}').toString("base64")
// eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9

payload(JWT Claim Set)

new Buffer('{"iss":"John Doe","exp":1434290400000,"username":"john","age":25,"iat":1434286842654}').toString("base64")
// eyJpc3MiOiJKb2huIERvZSIsImV4cCI6MTQzNDI5MDQwMDAwMCwidXNlcm5hbWUiOiJqb2huIiwiYWdlIjoyNSwiaWF0IjoxNDM0Mjg2ODQyNjU0fQ==

signature

var crypto = require('crypto');

var secretKey = 'secret';
var headerAndClaimSet = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJKb2huIERvZSIsImV4cCI6MTQzNDI5MDQwMDAwMCwidXNlcm5hbWUiOiJqb2huIiwiYWdlIjoyNSwiaWF0IjoxNDM0Mjg2ODQyNjU0fQ';

crypto.createHmac('sha256', secretKey).update(headerAndClaimSet).digest('base64')
// jzvwdy5mQuzkEenNEFeRlSytvB7+X7NVAvtTDr1jP0Q=

JWT의 특징

  • 토큰 자체가 데이터를 가지고 있음
    • 누구나 열어볼 수 있기 때문에 중요한 정보는 넣으면 안됨
  • 다른 토큰보다 길이가 길다
  • 토큰을 강제로 만료시킬 방법이 없다.
  • 뱅킹서비스, 주신관련 서비스 등 실시간 보안이 중요한 곳은 JWT를 쓰기보다 DB에 부담을 주더라도 DB에 토큰을 넣는 방식을 선택하는 게 나을 수 있음
  • 보안보다 사용성이 중요한 어플리케이션에선 정보를 넣을 수 있는게 큰 장점이 될 수 있음
  • 만료기간이 짧은 access 토큰과 달리 refresh 토큰은 만료기간이 매우 김

토큰 확인 절차

case1: access token과 refresh token 모두가 만료된 경우 -> 에러 발생
case2: access token은 만료됐지만, refresh token은 유효한 경우 -> access token 재발급
case3: access token은 유효하지만, refresh token은 만료된 경우 -> refresh token 재발급
case4: accesss token과 refresh token 모두가 유효한 경우 -> 다음 미들웨어로

세션-쿠키 방식과 비교

세션-쿠키 인증 방식


세션-쿠키 인증방식은 로그인에서 아직까지 제일 많이 쓰이는 인증방식

쿠키(Cookie)

  • 클라이언트(브라우저)의 저장소
  • 브라우저에 300개 까지 저장가능, 도메인당 20개 까지, 개당 4kB까지 저장
  • 구성 요소
    • Name-value
    • 유효기간(MaxAge or Expires)
    • 도메인(Domain) : 쿠키를 전송할 도메인
    • 경로(Path) : 서버가 라우팅 할 때 사용하는 경로, default : "/"
      • 서버에서 Path 하위 모든 route에 쿠키가 전송 됨
    • Secure : true로 설정된 경우 HTTPS인 경우에만 쿠키를 전송
    • HttpOnly : 자바스크립트에서 브라우저의 쿠키에 접근 여부를 결정
      • true : js에서 쿠키접근 불가능
      • false(default) : XSS 공격에 취약
    • SameSite : Cross-Origin 요청을 받은 경우, 쿠키 전송 여부 결정
      • Lax : Cross-origin 요청 시 'GET' 메소드에 대해서만 쿠키 전송
      • Stric : Same-site 인 경우에만 쿠키 전송
      • None : 항상 쿠키 전송 가능하지만 Secure 옵션 활성화 필요

세션(서버의 저장소)

  • 서버쪽 쿠키, key-value로 이루어진 객체
  • 서버에선 클라이언트 구분을 위해 세션 ID를 부여
    • 클라이언트가 request를 보내면 서버가 헤더에 쿠키를 담아 보낼 때 세션ID를 보냄
  • 보안면에서 쿠키보다 우수
  • 사용자가 많아지면 서버메모리를 많이 차지하게 됨

세션과 쿠키의 차이

  • 비슷 함, 결국 세션도 쿠키를 이용하여 서버에 저장 하는 것
  • 브라우저 종료를 세션에 알려주려면 beforeunload 이벤트를 이용 할 수 있음

토큰방식(JWT)과 세션-쿠키 방식의 차이

  • 세션쿠키 방식은 세션ID만 보냄
  • 세션이 서버의 메모리를 필요로 함

적용했던 프로젝트 복습

jasonwebtoken module을 사용(npm i jsonwebtoken)

jasonwebtoken에선 access token과 refresh 토큰을 어떻게 관리할까?

  • 토큰을 생성만 하고 저장은 내가 하는 것

// tvcf에 적용된 방식 분석

tvcf에 적용된 방식 분석


tvcf의 토큰 만료기간은 1일이다.
refresh 토큰은 어디에 보관되는가?

  • 로컬스토리지엔 access token만 보인다.
  • cookie엔 로그인 전후에 변화가 없는 것으로 보아 서버사이드에 보관되는 것으로 추정된다.
    • 세션이나 DB 둘중에 한 곳에 보관 될 것 같다.(DB에 보관될 것으로 추정)

내가 적용한 JWT(npm jsonwebtoken)와 tvcf에서 적용한 방식 차이 분석

{
  "id": 9,
  "iat": 1626355500
}
  • 나는 refresh 토큰을 발행하지 않고 로컬 스토리지에 access 토큰을 영구저장했었음
    토큰을 통해 전송 되는 것은 user.id 하나만 전송했었음
  • refresh 토큰 개념이 없었고 apollo client의 캐싱 기능을 사용해서 생각보다 jwt방식이지만 토큰의 페이로드에 많은 정보를 담을 필요가 없었던 것 같다.

결론

JWT 인증 구현 방식은 많다.
어느것을 정답이라고 말하긴 어렵다.

나는 미들웨어로 클라이언트에서 요청을 받을 때마다 verify 후 보냈었는데 회사의 서버에선 어떻게 로직이 구성되어 있는지 궁금하다.

참고 문헌

profile
움직이는 만큼 행복해진다

0개의 댓글