JWT를 공부하기에 앞서서, Authentication과 Authorization 에 대해 먼저 알아보자.
쇼핑몰 사이트에서 어떠한 행동(마이페이지 보기, 장바구니 담기, 구매하기)을 할 때마다
로그인(인증)을 요구하면 사용자 입장에서는 굉장히 귀찮을 것이다.
따라서, 서버측에서는 사용자가 한 번 로그인을 수행하면 일정 시간 동안은 로그인을 유지시켜줘야 한다.
이렇게, 로그인을 유지시켜주는 데에는 Cookie와 Session이 사용된다.
<cookie>웹에서 쿠키의 개념은, 웹 서버에서 생성해서 웹 브라우저(클라이언트)에게 전달하는 작은 데이터를 의미한다.
웹 브라우저는 이 데이터를 자신의 메모리에 저장해서, 같은 웹서버를 방문할 때 쿠키와 함께 Request를 보내는 식으로 통신한다.
장점 : 서버측에서 쿠키 데이터를 저장하는게 아니라, 클라이언트 측에서 보관하여 사용하기 때문에 서버 저장 공간의 부담을 줄일 수 있다. 이렇게 서버가 클라이언트에 대한 정보를 저장하지 않고, 요청마다 독립적으로 처리하는 것을 HTTP프로토콜에서는 Stateless하다고 부른다.
Stateless는 곧 API가 RESTful하다는 것을 의미한다.
단점 : 로그인을 유지하려면, 아이디와 비밀번호같은 민감한 데이터들이 쿠키에 들어가므로, 보안에 취약할 수 있다.
<session>세션은 쿠키의 단점인 보안을 강화한 것으로, 중요한 정보는 서버의 세션에 저장을 하고, 쿠키에는 그 정보의 위치(경로)에 대한 데이터값(session id)만 담아서 통신한다.
장점 : 민감한 데이터가 쿠키에 담기는 것이 아니기 때문에, 쿠키만 사용하는 것보다 보안이 강화된다.
단점 : 쿠키만 사용했을 때의 장점이었던 서버 저장 공간 절약이 없어진다. 즉, 서버 저장 공간을 소모하기 때문에, Stateless하지 않게 된다.
<JWT>쿠키와 세션의 단점을 개선한 방식이 JWT이다. ( JWT에도 많은 단점이 존재하긴 한다. )
JWT는 JSON Web Token의 약자로, JSON형식의 데이터를 웹에서 사용하기 위해 암호화 처리한 토큰을 의미한다.
JWT 공식 홈페이지를 살펴보자.
위 페이지에 들어가보면, Encoded와 Decoded 가 있고, 상단에는 암호화 알고리즘을 선택할 수 있다.
Decoded부분을 살펴보면, Header, Payload, Verify Signature 영역이 나뉘어진 것을 확인할 수 있고, Encoded부분에서는 이 세 영역을 .을 통해서 분리한다.
위 3가지 영역을 통해 생성된 Encoded 된 토큰을 살펴보면, 무슨 뜻인지 도저히 모르겠는 암호화된 코드가 생성된다. 이 토큰이 바로 JWT이다.
위의 특징으로 JWT는 인증과 인가에 대해서 처리할 수 있다.
장점
이제 JWT가 어떤 방식으로 인증과 인가를 수행하는지 알아보자.
STEP
클라이언트 측에서 로그인 API를 통해 서버에 Request한다. (username, password 데이터 전달)
서버는 데이터베이스 서버에 접근해서, 데이터베이스와의 값을 비교하는 등의 내부 로직을 통해 로그인이 타당한지 판단한다.
로그인이 타당하다면, JWT를 발행하고 이 JWT를 Respond에 실어서 클라이언트에게 전달한다.
서버 측에서는 이 JWT에 대해서 일정 시간동안 기억을 한다.(로그인 유지)
또한, 다음 Request부터는 JWT를 보내라고 클라이언트측에게 알린다.
클라이언트는 JWT와 함께, 필요로 하는 다른 Request를 보낸다. (인증) - (ex. 마이페이지 조회 요청)
서버는 JWT의 SIGNATURE를 확인하고, JWT 값이 올바르다면 클라이언트에게 원하는 것을 Respond해주고, 올바르지 않다면 클라이언트의 요청을 거부한다. (403 Error 전달)
어떠한 과정으로 JWT를 사용되는지 공부하였다. 이제 JWT를 코드로 어떻게 사용하는지 알아보자.
JWT 모듈을 사용하기 위해, npm 사이트에서 검색한다.
jsonwebtoken 모듈을 설치한다.
npm install jsonwebtoken
공식문서의 예시 코드를 사용해보자.
var jwt = require('jsonwebtoken'); // 모듈 불러오기
var token = jwt.sign({ foo: 'bar' }, 'shhhhh'); // 토큰 생성하기
console.log(token) // xxxxxxxxxxx.xxxxxxxxxx.xxxxxxxxxxx
var decoded = jwt.verify(token, 'shhhhh');
console.log(token) // { foo : 'bar' }
위 코드에서, token을 생성하기 위해 jwt.sign(PAYLOAD , SECRET) 으로 비밀번호를 입력하도록 한다. 이 비밀번호는 JWT의 3가지 영역 중 VERIFY SIGNATURE 에 영향을 준다.
이후 콘솔을 살펴보면, token이 생성된 것을 확인할 수 있다.
마찬가지로, jwt.verify(토큰, SECRET) 을 통해 값을 해독하여 PAYLOAD의 데이터 값을 확인할 수 있다. 이렇게 JWT를 Node.js 환경에서 사용할 수 있었다.
하지만, 우리가 이렇게 코드를 작성해서 Github 같은 페이지에 공유한다면, 'shhhhh' 같은 비밀번호는 외부에 유출되면 안된다. 따라서 .env 환경변수 파일을 사용해야 한다.
.env는 environment를 의미하고, 환경변수를 모아둔 파일을 칭한다.
환경변수란?
환경 변수(環境 變數, 영어: environment variable)는 프로세스가 컴퓨터에서 동작하는 방식에 영향을 미치는, 동적인 값들의 모임이다.
wikipedia
따라서, 환경변수로는 비밀번호, 포트번호 등 외부에 노출되면 안되는 변수들을 지정해서 사용한다.
우리는 이 환경변수들을 .env 파일에 모아서 작성해놓고,
Github에 공유할 때에는 이 환경 변수 파일을 git ignore을 적용시키면 된다.
Node.js 환경에서는 dotenv 이라는 모듈이 있다.
공식문서에 따라서 먼저 모듈을 설치하고,
npm i dotenv
공식문서에 따라 사용한다.
먼저, 프로젝트의 root 디렉토리에 .env 파일을 생성해야 하고, 해당 파일에 환경변수들을 정의하면 된다.
//.env
PORT = 3456 // 포트번호
MY_KEY = 'shhhhh' // 비밀번호
env 파일을 작성할 때의 규칙은, 선언자(const, let, var) 를 사용하지 않고, 모든 변수는 대문자로 작성되야 하고, snake_case 방식으로 변수를 작성한다.
이제 그냥 환경변수를 필요로 하는 파일에서 다음과 같이 사용하면 된다.
var dotenv = require('dotenv');
dotenv.config();
console.log(process.env.MY_KEY)
process.env.변수명 방식으로 사용한다.