사용자의 개인정보는 매우 민감하게 다뤄야 한다.
대부분의 사용자는 같은 비밀번호를 여러곳에서 쓰는 경우가 많은데, 한 사이트에서 비밀번호가 유출될 경우, 다른 사이트의 보안도 위험해 진다.
혹여나 DB가 유출될 경우를 대비해 비밀번호 같은 민감한 정보는 암호화가 필수다.
오늘은 Node.js에서Bcrypt
와JWT
모듈을 사용해 DB에 암호화된 비밀번호 저장, 비밀번호가 일치 할 경우 Token을 발급해 신뢰성을 부여한다.
오늘 사용할
Bcrypt
모듈은 단방향 암호화를 지원한다.
단방향 암호화는 해시 알고리즘을 통해 해시값이 출력 되는데, 같은 값에 대해서는 동일한 해시값을 가진다.
평문에 단방향 암호화에 해시 알고리즘을 적용하면 해시값이 간단해진다.
이를 보완하기 위해 평문에 특정 문자를 추가 한뒤 해시 알고리즘을 실행하는데, 이를 Salting(솔팅) 이라고 한다.
솔팅을 하더라도 해시 결과값은 그렇게 복잡하지 않다. 그래서 우리는 일정 횟수만큼 해쉬를 반복 실행해 해쉬값을 더욱 어렵게 만들어 DB가 노출되어도 민감한 정보가 노출되지 않게 한다.
const bcrypt = require("bcrypt");
const password = '패스워드';
const saltRounds = 12;
const makeHash = async (password, saltRounds) => {
return await bcrypt.hash(password, saltRounds);
}
//예시 '$2b$12$LexMcb9ly1CZ0HfSr.hcL.qCFflGTD6o/7vxjHzEb6dxxvQAgxlae'
npm으로 bcrypt
모듈을 설치 하고 불러온다. 그 후 makeHash 함수를 선언해 bcrypt.hash
함수를 넣어준 뒤, 패스워드와 솔팅 횟수(해쉬 반복 수)를 넣어준다.
해당 함수 실행시 넣은 패스워드를 해쉬 알고리즘을 거친 뒤 암호문이 리턴 된다.
새로 입력한 패스워드와 DB에 저장된 암호화된 패스워드를 비교하고자 매번 새로운 패스워드를 암호화 한 뒤 비교를 하게 될 경우, 암호화에 소스가 많이 들기 때문에 자원낭비가 심하게 된다.
이를 위해 평문으로 입력된 패스워드와 해쉬 알고리즘을 거친 암호문을 같이 특정 함수에 넣을 경우 결과 값을boolean
으로 반환해 준다.
const checkHash = async (password, hashedPassword) => {
return await bcrypt.compare(password, hashedPassword)
}
checkHash(패스워드, 암호화된 패스워드);
위의 checkHash
함수를 패스워드와 암호화된 패스워드를 같이 넣을 경우 맞으면 true 틀리면, false 값을 반환해준다.
JWT는 사용자와 서버 사이에 정보를 JSON 개체로 안전하게 전송하기 위한 개방형 표준 입니다. 크기가 작아 데이터 전송에 무리가 없으며, 전자 서명이 되어있어 검증 과정을 거쳐 , 신뢰성을 가지고 있다.
JWT는 Header와 payload 그리고 signature 3가지로 이루어져 있으며, 구성 요소들은 dot(.)
으로 구분되어 있다.
JWT의 첫번째 구성 요소. 일반적으로 2가지의 정보를 담고 있다.
alg
: Signature 을 만드는데 사용한 알고리즘 정보typ
: Token의 타입{
"alg" : "HS256",
"typ" : "JWT"
}
위는 알고리즘 알고리즘 HS256사용, Token 타입 JWT라는 의미다.
JWT의 두번째 구성요소 실질적으로 전달해야 하는 정보들이 들어있다. Payload에 담긴 정보는 Claim이라고 부르며 3가지 종류가 존재한다.
iss
: 토큰 발급자 sub
: 토큰 제목aud
: 토큰 대상자exp
: 토큰 만료 시간iat
: 토큰 발급 시간nbf
: 토큰 활성화 시간jti
: JWT의 고유 식별자{
"exp": "1245678900", //Registered Claims
"https://velopert.com/jwt_claims/is_admin": true, //Public Claims
"user_id" : 12345123 //Private Claims
}
JWT의 세번째 구성요소 Header와 Payload의 인코딩된 내용을 더하고 Secret Key와 알고리즘을 이용해 생성된 암호화된 값을 나타낸다.
암호화된 값을 Signature와 비교해 JWT의 신뢰성을 확인 할 수 있다. 서버에서 관리하는 Secret Key가 달라질 경우 Signature가 달라지기 때문에 새로운 토큰을 발급 받아야 한다.
JWT 토큰을 발급받기 위해서는 jwt 모듈을 다운받아야 한다.
const jwt = require('jsonwebtoken');
const payLoad = { foo: 'bar' };
const secretKey = 'mySecretKey';
const jwtToken = jwt.sign(payLoad, secretKey);
console.log(jwtToken)
=> 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIiLCJpYXQiOjE2NTA1NTYxMzZ9.YAMgUMLhiVUwkRTr2rpOrIyWN0cTGLxsxZBqLAaKWUU'
payLoad 영역은 실제로 전달할 내용을 넣는다. 해당 영역은 공개 되기에 민감한 정보를 넣지 않는다.
secretKey는 코드상에서 노출되면 보안의 위험이 있기 때문에 환경변수로 주로 관리한다. 두개의 값을 넣고 `jwt.sign` 함수를 실행 시키면 3가지 dot(.) 으로 분류된 JWT 토큰이 발급 된다.
## JWT 검증
````js
const decoded = jwt.verify(jwtToken, secretKey); // (1)
console.log(decoded)
=> { foo: 'bar', iat: 1650555667 }
jwt.vertify
함수에 JWT토큰과 발급할때 사용한 secretKey를 인자로 넣어서 실행하면 일치하지 않을 경우 에러를 발생한다.
값으로 foo: 'bar'
만 넣었는데 iat
값도 같이 나와버렸는데, 이는 토큰 생성 시간을 표시한다.
개인정보 저장에 암호화는 필수다. 나의 안일함으로 암호화를 하지 않은 데이터가 유출 되었을 경우 사용자는 피해를 고스란히 받게 된다. 만에 하나 사고가 나더라도 데이터 유출을 막을 수 있는 데이터 암호화를 생활화 하자.