JWT (JSON Web Token)

JWT (JSON Web Token)

JWT란, 2010년도에 개발되었으며 JSON을 이용하여 웹토큰을 주고받는 것을 의미한다.

이 이미지는 대체 속성이 비어있습니다. 그 파일 이름은 image-81-1024x419.png입니다

JWT 구성

JSON 오브젝트 베이스안에 Header, Payload, Signature 로 나누어져 있다. 여기에 사용자의 인증에 대한 모든 정보를 담는 것이다.

  • Header
    • alg : 사용하는 알고리즘
    • typ : 타입
  • Payload
    • 인코딩된 주고 받으려는 정보
  • signature
    • 인코딩한 header / payload 부분 뿐 아니라 , 인코딩 하기 위해 사용하는 서버의 비밀키를 함께 인코딩

서버에서만 알고 있는 비밀키를 함께 인코딩해둠으로써, 사용자가 악의적으로 payload에 있는 정보를 바꾸더라도 signature에 있는 정보를 이용하여 해당 정보가 변경되었는지 여부를 파악할 수 있다.

요약하자면, JWT 는 json 파일 안에 필요한 모든 데이터를 넣어서 주고 받을 수 있고, 데이터를 서버에서만 알고 있는 시크릿키를 이용하여 인코딩하기 때문에 정보에 유효성을 확인할 수 있다.

JWT 를 이용한 Flow

이 이미지는 대체 속성이 비어있습니다. 그 파일 이름은 image-82-1024x478.png입니다
  1. 클라이언트가 서버에 로그인 요청을 보낸다.
  2. 서버는 데이터베이스에서 사용자가 보낸 id/pwd 가 일치하는지 확인한다.
  3. 유효한 사용자라면, 서버는 JWT 를 생성한다.
  4. 생성된 JWT 를 헤더에 넣고 응답을 보낸다.
  5. 앞으로 생기는 모든 클라이언트 요청의 헤더에 JWT 를 실어서 보낸다.
  6. 서버는 JWT 가 유효한지 / 내용이 수정 되었는지 / 만료되었는지 / 사용자에 대한 저보가 정확한지 등의 유효성 검사를 한 후, 요청받은 데이터를 보내준다.
  • JWT를 사용하면 좋은점 👍
    • 서버에 state가 없다. 세션을 이용할때는 서버에 세션이라는 별도의 상태가 필요했다면 , JWT 는 한번 json으로 만들어서 client에 보내주고 검증만 하면 되기 때문이다.
    • 서버를 확장하거나, 마이크로 서비스를 이용하거나, 분산형 시스템으로 만들어도 서버간의 네트워크 요청을 통해 사용자 검증을 하지않아도, JWT를 디코딩할 수 있는 비밀키만 가지고 있으면 된다.
  • JWT를 사용하면 안좋은 점 👎
    • JWT 자체가 단점이 될 수 있다. 서버와 클라이언트 간에 이 중요한 JWT를 헤더를 통해 계속해서 주고받기때문에, 만약 영원히 만료되지않는 JWT를 서로 주고 받는다면 악용될 여지가 있다.

Nodejs – JWT 테스트

const jwt = require("jsonwebtoken");

const token = jwt.sign(
  {
    id: "userId",
    isAdmin: true, // 꼭 필요한 정보만 담기
  },
  "!ENCJV2hMXgeepR*Nih^!H88K#f5^B5h" // 권고 사이즈 : 32 bytes
);

console.log(token); // eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6InVzZXJJZCIsImlzQWRtaW4iOnRydWUsImlhdCI6MTY0NzY3NjUyOX0.jfcyYcFxwT0v6PWCgcl85S6eTdXztZZLcsHLL2nCeSc

payload 는 토큰에 담고 싶은 정보를 넣으면 되는데, 사이즈가 너무 커지면 계속 클라이언트와 주고받는데 네트워크 데이터를 많이 소모할 수 있으므로 필수적인 데이터만 넣는 것이 중요하다.

비밀키 생성도 쉽게 알아낼 수 없는 키를 생성해주는 사이트를 이용하는 것이 좋다. (ex. !ENCJV2hMXgeepR*Nih^!H88K#f5^B5h) 권고되는 사이즈는 32 bytes (256 bits) 이다.

  • 참고 사이트 : https://www.lastpass.com/features/password-generator

JWT 디코딩

jwt.io 이용

위 디코딩 사이트 (jwt.io) 를 보면 secret 키는 노출이 되지않는 것을 알 수 있다. 그럼 위 JWT payload 데이터를 변경 후, 비교해보면 어떨까 ?

const jwt = require("jsonwebtoken");

const secret = "!ENCJV2hMXgeepR*Nih^!H88K#f5^B5h";
const token = jwt.sign(
  {
    id: "userId",
    isAdmin: true,
  },
  secret
);

console.log(token); // eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6InVzZXJJZCIsImlzQWRtaW4iOnRydWUsImlhdCI6MTY0NzY3NjUyOX0.jfcyYcFxwT0v6PWCgcl85S6eTdXztZZLcsHLL2nCeSc

const edited = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6InVzZXJJZCIsImlzQWRtaW4iOmZhbHNlLCJpYXQiOjE2NDc2NzY1Mjl9.BYrg0QuQI5QuVHfIEs7JYc0IoKtMqCOsCmKFIjTzZKI"; // 페이로드 데이터를 임의로 변경하여 생성한 JWT
jwt.verify(edited, secret, (error, decoded) => {
  console.log(error, decoded);
}); 

Invalid signature 에러가 나는 것을 볼 수 있다.

소스 코드 개선 (jwt 토큰 만료 시간 설정)

const jwt = require("jsonwebtoken");

const secret = "!ENCJV2hMXgeepR*Nih^!H88K#f5^B5h";
const token = jwt.sign(
  {
    id: "userId",
    isAdmin: true,
  },
  secret,
  {
    expiresIn: 2, // 만료시간 2초 설정
  }
);

jwt.verify(token, secret, (error, decoded) => {
  console.log(error, decoded);
});

한번 토큰을 발행하면, 특정 정보를 바꾸면 전체적인 토큰이 다 변경되므로 정보가 수정되어서 보안에 문제가 생기는 점은 염려하지 않아도 된다.