오늘 내가 배운 것
1. 내장 모듈 crypto
2. JWT - Signature
3. JWT - 토큰 만들기
4. JWT - 토큰 검증하기
1. 내장 모듈 crypto
Node.js의 내장 모듈이기 때문에 require 해서 사용해야 한다. crypto는 클래스이다..
const crypto = require("crypto")
const salt = process.env.SALT || "web7722"
const hash = crypto.createHmac(`sha256`, salt).update(평문).digest("hex") // createHmac은 `정적메서드`다.
JWT에서 Signature을 만드는 목적이 headerd와 payload가 위조, 변조되었는지 확인하기 위해서인데,
salt값이 없으면 sha256을 이용하여 다 똑같은 암호 값을 가지게 된다.
그래서 salt 값을 넣어서 암호화를 진행한다. 복호화를 했을 때 똑같은 값을 얻기 위해서는 반드시 salt를 이용하여 암호화를 해야 한다.
그리고 이 salt 값을 이용해서 복호화를 할 수 있기 때문에 절대로 공유되거나 노출되지 않도록 조심해야 한다.
update에는 인자값으로 암호화를 할 평문의 내용을 적는다.
digest는 결과를 어떤 인코딩으로 나타낼 것인지를 쓰면 된다.
hex를 이용해서 16진수로 인코딩을 하면 평문의 글자가 몇 글자든 64글자로 나온다.
2. JWT - Signature
header와 payload가 위조, 변조되었는지 확인할 수 있다. ( 4번의 토큰 검증하기에서 방법을 확인)
평문 -> hash -> base64로 인코딩을 한 것이다.
그래서 base64를 아무리 디코딩을 하더라도 salt가 없는 없다면 hash값 밖에 못 얻기 때문에 보안에 좋다.
3. JWT - 토큰 만들기
내장 모듈 crypto를 이용한 토큰 만들기
const crypto = require("crypto")
const header = {
alg: "HS256",
typ: "JWT",
}
const payload = {
sub: "1234567890",
userid: "admin",
username: "admin",
iat: 1516239022,
}
function encode(obj) {
return Buffer.from(JSON.stringify(obj)).toString("base64url")
}
const header64 = encode(header)
const payload64 = encode(payload)
console.log(header64, payload64)
const 평문 = header64 + "." + payload64
console.log(평문)
const signature = crypto
.createHmac("sha256", "web7722")
.update(평문)
.digest("base64url")
console.log(signature)
이 토큰을 탈취해서 접근을 시도하면 접근이 가능한가?
접근이 가능하다. 그래서 여러 가지 방법을 이용해서 최대한 보안에 신경을 쓴다.
예를 들면 액세스 토큰과 리프레시 토큰을 이용한 방법이다.
이 방법은 유효시간을 짧은 액세스 토큰과 비교적 긴 리프레시 토큰을 설정하여 액세스 토큰의 시간 동안 작업을 하다가 시간이 만료되면 새로 발급을 받아야 하는데 그 새로 발급을 받을 수 있는 시간은 리프레시 토큰의 기간 동안 가능하다.
리프레시 토큰의 기간도 만료가 되면 재로그인을 요청하여 위의 과정을 반복하는 방법이 있다.
토큰의 만료시간을 짧게 두어 탈취가 되더라도 그 시간에만 사용가능하게 하는 방법이다.
4. JWT - 토큰 검증하기
token을 뜯어보면 .(점)을 기준으로 header, payload, signature로 이루어져 있다.
여기서 header와 payload를 가져와서 다시 hash를 진행한다.
그리고 나온 값을 새로운 hash라고 했을 때, 기존의 hash와 비교를 해서 같으면 이 토큰은 사용가능한 토큰이 된다.
verify(token, salt){
const [header, payload, signature] = token.split('.')
const newSignature = this.createSignature([header, payload], salt)
if(newSignature !== signature) {
throw new Error("토큰이 다릅니다.")
}
return this.decode(payload)
}
const payload = jwt.verify(token, salt)
JWT 공식 웹사이트
https://jwt.io/
클래스를 이용하여 토큰을 만들고, 검증하기
const crypto = require("crypto")
//MVC - Service 에서 처리해야할 코드들이다...
class JWT{
constructor({crypto}){
this.crypto=crypto
}
sign(data, options={}){
const header = this.encode({typ:"JMT", alg:"HS256"})
const payload = this.encode({...data, ...options})
const signature = this.createSignature([header, payload])
return [header,payload,signature].join(".")
}
verify(token, salt){
const [header, payload, signature] = token.split('.')
const newSignature = this.createSignature([header, payload], salt)
if(newSignature !== signature) {
throw new Error("토큰이 다릅니다.")
}
return this.decode(payload)
}
encode(obj){
return Buffer.from(JSON.stringify(obj)).toString("base64url") //base64
}
decode(base64url){
return JSON.parse(Buffer.from(base64url, "base64url").toString("utf-8"))
}
createSignature (base64url, salt="web7722"){
const data = base64url.join(".")
return this.crypto.createHmac("sha256", salt).update(data).digest("base64url")
}
}
const jwt = new JWT({crypto})
const token = jwt.sign({userid:"web7722", username:"baek"})
console.log(token)
//eyJ0eXAiOiJKTVQiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyaWQiOiJ3ZWI3NzIyIiwidXNlcm5hbWUiOiJiYWVrIn0.jhQDnAM4rrsPKDmvFi2BqiOKrGT7SClWebDuVnjTdgY
const payload = jwt.verify("eyJ0eXAiOiJKTVQiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyaWQiOiJ3ZWI3NzIyIiwidXNlcm5hbWUiOiJiYWVrIn0.jhQDnAM4rrsPKDmvFi2BqiOKrGT7SClWebDuVnjTdgY","web7722")
console.log(payload)
'시작 > TIL(Today I Learned)' 카테고리의 다른 글
230120 - 페어코딩 - 토큰을 이용한 로그인 (0) | 2023.01.25 |
---|---|
230119 - javascript - 카카오로그인(kakao login) API (0) | 2023.01.25 |
230118 - JavaScript - JWT, base64, 인코딩과 디코딩 (0) | 2023.01.18 |
230117 - JWT , 쿠키와 세션, 암호화 (0) | 2023.01.17 |
230116 - JavaScript - Sequelize을 이용한 CRUD (0) | 2023.01.17 |
댓글