JWT (Json Web Token)

유진·2023년 2월 1일
0

모각소 3주차

JWT

= 정보 보호 X, 인증 O, 위조 방지 O → 서버는 유효한 토큰인지 확인하는 것이 중요함

  • 선택적 서명 및 선택적 암호화를 사용하여 데이터를 만들기 위한 인터넷 표준

  • 유저를 인증하고 식별하기 위한 토큰 기반 인증

  • 토큰은 세션과 달리 서버가 아닌 클라이언트에 저장됨 → 메모리나 스토리지를 통해 세션을 관리했던 서버의 부담을 줄여줌

  • JWT를 사용하면 RESTful과 같은 Stateless환경에서 사용자 데이터를 주고 받을 수 있다.

  • 토큰을 클라이언트에 저장하고 요청시 단순히 HTTP 헤더에 토큰을 첨부하는 것만으로도 단순하게 데이터를 요청하고 응답을 받을 수 있다.

핵심적 특징

  • 토큰 자체에 사용자의 권한 정보나 서비스를 사용하기 위한 정보가 포함됨 (self-contained)
  • 데이터가 많아지면 토큰이 커질 수 있음 → 토큰이 한 번 발급된 이후 사용자의 정보를 바꾸더라도 토큰을 재발급하지 않는 이상 반영되지 않음

JWT 사용 순서

  1. 클라이언트 사용자가 아이디, 패스워드를 통해 웹서비스 인증 (로그인)
  2. 서버에 서명된 JWT를 생성하여 클라이언트에게 응답으로 돌려줌 (클라이언트가 토큰 저장)
  3. 클라이언트가 서버에 데이터를 추가적으로 요구할 때 JWT를 HTTP Header에 첨부
    1. 저장해둔 토큰을 포함시켜서 서버에게 요청보냄
  4. 서버에서 클라이언트로부터 온 JWT 검증

JWT 구조

  • Header
    • JWT에서 사용할 Type & Hash Algorithm 종류
  • Payload
    • 서버에서 첨부한 사용자 권한 정보 & 데이터
  • Signature
    • Header, Payload를 Base64 URL-safe Encode를 한 이후
    • Header에 명시된 해시함수 적용하고 개인키 (Private Key)로 서명한 전자서명이 담겨있음

ex) 전자서명 알고리즘으로 타원 곡선 암호화 (ECDSA)를 사용한다고 가정

  • 전자서명 : 비대칭 암호화 알고리즘 사용
  • 암호화를 위한 키 != 복호화를 위한 키
    • 암호화(전자서명) = 개인키
    • 복호화(검증) = 공개키
// 전자서명
Sig = ECDSA (
				SHA256(B64(Header).B64(Payload)), 
				PrivateKey
			);

이 값을 JWT로 표현

→ 위에서 만든 전자서명도 Base64 URL-safe Encode로 처리해서 합쳐야 함

JWT = B64(Header).B64(Payload).B64(Sig);

JWT 장점

  • Header와 Payload를 사용하여 Signature를 생성 → 데이터 위변조 막을 수 있음
  • 인증 정보에 대한 별도의 저장소가 필요없다.
  • 필요한 모든 정보를 자체적으로 가지고 있음 (self-contained)
    • 토큰에 대한 기본 정보
    • 전달할 정보
    • 토큰이 검증됨을 증명하는 서명
  • 서버는 무상태 (Stateless)가 되어 서버 확장성이 우수해질 수 있음
    • vs 세션 : 클라이언트 인증 정보를 저장함
  • 토큰 기반으로 다른 로그인 시스템에 접근 및 권한 공유가 가능함
    • vs 쿠키
  • OAuth : 페이스북, 구글 등 소셜 계정을 이용하여 다른 웹서비스에서도 로그인 가능
  • 모바일 어플리케이션 환경에서도 잘 동작
    • vs 모바일 세션 사용 불가능

JWT 단점

  • self-contained
    • 토큰 자체에 정보를 담고 있으므로 양날의 검이 될 수 있다
  • 토큰 길이
    • 토큰의 Payload에 3종류의 Claim을 저장함
    • 정보가 많아질수록 토큰의 길이가 늘어남 → 네트워크 부하
  • Payload 인코딩
    • Payload 자체는 암호화된 것이 아님 = Base64로 인코딩 된 것 → 중간에 Payload를 탈취하여 디코딩하면 데이터를 볼 수 있음 → Payload에 중요한 데이터를 넣으면 안됨
  • Store Token
    • stateless 특징을 가지고 있음 → 클라이언트 측에서 토큰을 관리하고 저장함 → 토큰을 탈취당하면 대처하기 어려움

REST API 인증처리 (JWT 생성 & 검증)

  • JWT - Sign, Verify

(1) JWT 모듈 설치

// jsonwebtoken 모듈 설치
npm install --save jsonwebtoken
// 모듈 설치하면 자동으로 package.json 파일에 추가됨
"dependencies":{
	"jsonwebtoken" : "^8.5.1"
}

(2) 비밀키 모듈 생성

JWT 서명을 생성하기 위해서 비밀키가 필요함 → 외부 노출되면 안됨 → .gitignore

// config/jwt.js
let jwtObj = {};
jwtObj.secret = 'apple'; // 비밀키 = apple
module.exports = jwtObj

(3) REST API 생성 - JWT Sign (토큰 생성) - sign() 메소드

routes/index.js

const express = require('express');
const router = express.Router();
const jwt = require('jsonwebtoken');

// 기본 구조
jwt.sign(
		payload, 
		secret_Or_privateKey, 
		[options, callback]
);

// 예시
jwt.sign(
		{foo: 'bar'},
		cert,
		{algorithm: 'RS2556'}, function(err, token){console.log(token);}
);

// 회사 코드
let token = jwt.sign(
	{
		userIdx: userInfo[0].userIdx,
		isKeep: isKeep,
	}, // 토큰의 내용(payload)
	secret_config.jwtsecret, // 비밀키
	{
		expiresIn: expiresIn,
		subject: "userInfo",
	}, // 유효 기간 365일
);

(4) JWT 권한 확인 - verify() 메소드

routes/index.js

// 기본 구조
jwt.verify(
		token,
		secret_Or_privateKey,
		[options, callback]
);
  • 첫 번째 인자 = token
    • 인증된 유효한 토큰인지 확인해야하므로 토큰이 필요함
    • 토큰 : 쿠키에 저장되어 있음 → 요청 객체에서 cookies 속성을 참조 → express는 자동으로 cookieparser 미들웨어가 등록되어 있음 → 회사 코드 : 로컬 스토리지에 토큰 저장해둠
  • 두 번째 인자 = secret key
    • 디코딩하기 위해서 인코딩에서 사용한 secret key가 필요함

!주의!

JavaScript의 비동기 처리로 인해 callback이 호출되기 전에 처리가 완료될 수 있습니다.

→ jwt.sign() 메서드를 Promise로 처리하고 async와 await로 동기 처리되게 해야 합니다.

Access Token & Refresh Token

  • JWT token
    • 토큰 자체가 HTTPS를 사용하지 않는 환경이거나 기타 보안 취약점으로 인해 노출되었을 때 어떻게 이 문제를 해결할 것인가?
    • 토큰 유효 기간 = 보통 1일
    • 인증 토큰을 탈취당했을 때, 어떻게 피해를 최소화할 수 있을까? → Refresh token
  • 서버의 리소스에 접근할 때 클라이언트 본인을 인증할 수 있는 Access Token으로 동작한다.

문제 1 ) JWT 유출 문제

  • 문제점 : JWT는 Stateless한 방식 → 서버측에서 이 토큰을 가지고 있는 클라이언트가 정말 클라이언트 본인인지 토큰을 탈취한 사람인지 확인할 수 없다

  • 해결법 : Refresh Token

    • 목적 : 사용자 인증 X, Access Token 생성
  • Access Token의 유효 기간은 짧게 설정

  • Refresh Token의 유효 기간은 길게 설정

  • 사용자는 Access Token과 Refresh Token 둘 다 서버에 전송 & 전자로 인증 & 만료되었을 시 Refresh Token을 이용하여 새로운 Access Token을 발급받음

  • 토큰을 탈취한 사람은 Access Token의 짧은 유효 기간이 지나면 사용할 수 없음

  • 정상적인 클라이언트는 Access Token 유효 기간이 지나도 Refresh Token을 이용하여 새로운 Access Token을 생성

→ 짧은 시간동안만 토큰을 사용할 수 있음

→ 주기적으로 재발급 = 토큰이 유출되어도 피해를 최소화

문제 2 ) 정상적인 클라이언트도 짧은 주기마다 다시 로그인해서 Access Token을 재발급받아야 함

  • 유효 기간이 긴 Request Token을 사용함
  • 정상적인 사용자는 Access Token이 만료되었을 때 → 서버측에 Request Token을 전송
  • 다시 로그인 안하고 서버에게 새로운 Access Token을 발급다을 수 있음

문제 3 ) Refresh Token의 탈취

  • Refresh Token 자체가 탈취당함 = 공격자가 Refresh Token의 유효 기간만큼 Access Token을 생성할 수 있음 → 서버의 검증 로직이 필요함

  • DB에 각 사용자의 Acces Token-Refresh Token을 1대1 매핑하여 저장함

  • 정상적인 사용자 : 기존의 Access Token으로 접근 → 서버 : DB에 저장된 Access Token과 비교 & 검증

  • 공격자 : 탈취한 Refresh Token으로 새로운 Access Token 생성 → 서버 : DB에 저장된 Access Token과 다른 것을 확인

  • DB에 저장된 Access Token이 아직 만료가 안된 상황 & 전송받은 Access Token이 DB에 저장된 Access Token과 다른 경우

    • 굳이 새로 발급할 필요가 없는데, 왜 다르지 ? == 탈취당했다고 생각
    • Refresh Token이 탈취당했다고 가정 → 두 Token 모두 만료시킴
  • 정상적인 사용자 : 자신의 Token이 만료됨 = 다시 로그인

  • 공격자 : Refresh Token이 만료되었으므로 정상적인 사용자의 리소스에 접근 불가

문제 4 ) 공격자가 Access Token을 먼저 생성

  • 공격자가 Refresh Token을 탈취
  • 정상적인 사용자가 Access Token을 다시 발급받기 전에 공격자가 먼저 Access Token을 생성 → Access Token 충돌 → 서버 : 두 토큰 모두 폐기
  • ietf 문서의 권장
    • Refresh Token 유효 기간 = Access Token 유효 기간
    • 사용자가 한 번 Refresh Token으로 Access Token을 발급받았으면 Refresh Token도 다시 발급받기

문제 4 ) Access Token & Refresh Token 둘 다 탈취

  • 방법이 없음
  • FrontEnd & BackEnd에서 로직을 강화하여 토큰이 유출되지 않도록 보완해야 함

Token의 저장 장소

  • 서버 : DB
  • 클라이언트 : 쿠키, 로컬 스토리지 등등
    • http-only 속성이 부여된 쿠키에 저장하는 것을 권장 → 우리 회사는 로컬 스토리지 → http-only 속성이 부여된 쿠키는 JavaScript 환경에서 접근할 수 없음 → XSS, CSRF 발생해도 쿠키가 노출되지 않음
    • 일반적인 쿠키, 로컬 스토리지는 JavaScript에서 자유롭게 접근 가능 → 보안 측면에서 권장되지 않음

0개의 댓글

관련 채용 정보