-
[Express.js] 인증/인가(Bcrypt, JWT)Back-End/Express.js 2021. 2. 4. 06:14반응형
🤯 서론
요즘 Express 공부에 대부분의 시간이 할애되는 것 같다... (공부를 가장한 구글링의 늪 👾👾)
오늘은, 로그인/회원가입 기능을 만들면서 적용한 비밀번호 암호화(Bcrypt)와 토큰발행(JWT)에 대해 간단히 포스팅하고자 한다.
📗 Bcrypt
1. Bcrypt 란?
1999년에 Niels Provos와 David Mazieres가 발표한 가장 강력한 단방향 비밀번호 해시 메커니즘 중 하나이다. C, C++, C#, Go, Java, PHP, Perl, Python, Ruby등의 언어를 지원한다. (해싱(hashing)은 임의의 길이의 데이터를 고정된 길이의 데이터로 매핑하는 것을 의미)
* 암호화의 종류에는, SHA, PBKDF2, Scrypt 등이 존재한다. 각각은 속도와 보완성에 따른 장단점이 존재한다.
Bcrypt는 단방향 해시 함수라는 특징을 가지며, 이는 곧 암호화된 메세지(digest)를 생성하더라도, 다시 평문으로 볼 수 없다는 강점이 있다.
즉, Bcrypt 모듈(Node.js)을 통해, 회원가입 시 받아온 비밀번호 정보를 암호화하여 DB에 저장할 수 있는 것이다.
2. Bcrypt의 문제점과 보완책
- 문제점
1) 동일한 비밀번호는 동일한 digest(해시값)을 생성한다. 비밀번호 별 해시값 테이블이나 해시패턴을 역추적하면 복호가 가능하다.
2) 해시 함수의 빠른 처리속도가 역으로 단점이 된다. 대량의 digest와 대조하면서 비밀번호 원본을 찾아낼 수 있는 것이다.
- 보완책
1) 솔팅(salting)
말 그대로 소금을 친다고 생각하자! 해시값 사이사이에 임의 문자열을 추가하여 digest를 생성하는 방법이다.
2) 키 스트레칭(key stretching)
입력한 비밀번호의 digest를 생성하면, 생성된 digest를 다시 해싱하여 해시값을 연장하는 것이다. 복호를 그만큼 지연시킬 수 있다.
3. Bcrypt 모듈 종류
Node.js에서는 bcrypt와 bcryptjs 두 가지 모듈을 제공한다. 차이점은, bcrypt는 C++, bcryptjs는 JS 기반이라는 것이다.
둘 중 아무 모듈이나 상관없지만, 나는 벤치마크 테스트에서 bcrypt가 더 빠르다는 글을 보고 단순하게 bcrypt를 적용했다.
(Node.js의 Bcrypt와 BcryptJS 벤치 마크 : ichi.pro/ko/post/116178465148098)
4. Bcrypt 모듈 설치 및 사용
- 설치
npm install bcrypt
- bcrypt.hash (회원가입)
// bcrypt 선언 const bcrypt = require('bcrypt'); // 비밀번호 해싱 const { password } = req.body; const hashedPassword = await bcrypt.hash(body.password, 10); // 유저DB 추가 usersDB.push({ id: Date.now().toString(), ...body, password: hashedPassword, })
bcrypt를 가져온 뒤, hash() 메서드를 통해서 비밀번호 값을 암호화할 수 있다.
첫 번째 인자로 값, 두 번째 인자로 salt round 수, 세 번째 인자로는 암호화된 비밀번호의 처리함수(DB저장 등)를 콜백으로 받는다.
또한, hashedPassword 에서 확인할 수 있듯이, bcrypt 암호화는 비동기로 처리해야 함을 알 수 있다.
반드시, 해싱이 진행되는 함수를 async로, bcrypt.hash() 메서드로 해싱하는 부분을 await 를 적용해야한다. (미적용시 undefined 반환)
* 공식문서를 보니, hashSync() 메서드를 활용하면 비동기로 해싱 처리가 가능한 것 같다!
위처럼, 내가 입력한 비밀번호가 암호화(해싱)되어 저장된 것을 알 수 있다.
- bcrypt.compareSync (로그인)
관리자들도 알 수 없도록 비밀번호는 암호화되지만, 로그인 시 비밀번호 일치여부를 확인할 수 있어야 한다.
이를 위한 메서드가 compare() 이다. (마찬가지로, compareSync() 는 비동기로 대조 처리를 하는 메서드이다.)
const userCompared = bcrypt.compareSync(password, userExisted.password);
위처럼, 메서드 인자는 첫 번째로 입력된 값(non-hashed), 두 번째로 비교될 값(hashed) 순으로 입력한다. (boolean을 반환)
아래는, compare() 을 사용한 예시이다.
// compare(data, encrypted, callback) bcrypt.compare(myPlaintextPassword, hash, function(err, result) { // result == true }); bcrypt.compare(someOtherPlaintextPassword, hash, function(err, result) { // result == false }); // compare(data, encrypted).then(asynchronous) bcrypt.compare(myPlaintextPassword, hash).then(function(result) { // result == true });
.then() 혹은 hashSync(), compareSync() 등 비동기 해싱을 하는 경우는, 회원정보를 DB에 입력/불러오기 하는 경우에 선호된다.
📗 JWT(Json Web Token)
1. JWT(Json Web Token) 이란?
사용자 정보를 JSON 객체에 담아, 이를 암호화하고 해싱 작업을 거쳐 문자열 토큰으로 생성하는 기술이다.
JWT는 위처럼, Header, Payload, Signature 세 영역으로 나뉘어져 있으며, 각각의 정보들이 해싱되어 위처럼 문자열 토큰이 된다.
- Header: 해싱 알고리즘(SHA-2 = HS256)과 토큰 타입(JWT)이 명시되어 있다. 토큰 해독을 위해 알고리즘을 참고한다.
- Payload: 토큰에 담을 정보(Claim)들이 들어있다. iat(Issued At Time)같은 표준 클레임과, 사용자 지정 클레임 등이 포함된다.
- Signature: 토큰을 인코딩하는 BASE64-URL과, header/payload를 '.'으로 구분, JWT에 사용된 secret key 등이 포함된다.
2. jsonwebtoken 설치 및 발급
- 설치
npm install jsonwebtoken
Node.js 에서 제공하는 'jsonwebtoken' 모듈을 설치 및 활용하면 된다.
- 발급
const jwt = require('jsonwebtoken'); const token = jwt.sign(req.body, 'MY_SECRET_KEY'); response = { success: true, message: 'SUCCESS', token: token, }
- jwt 변수에 'jsonwebtoken' 을 불러온다.
- sign() 메서드를 활용한다. 첫 번째 인자는 담을 정보(payload), 두 번째 인자는 Secret Key를 받는다.
- 세 번째 인자는 알고리즘으로, 미설정시 default인 SHA256 알고리즘이 적용된다.
* 다른 알고리즘(RS256) 을 적용하고자 하는 경우
// sign with RSA SHA256 var privateKey = fs.readFileSync('private.key'); var token = jwt.sign({ foo: 'bar' }, privateKey, { algorithm: 'RS256'});
3. JWT의 장단점
- 장점
- 주요정보가 토큰의 payload에 저장되어, 별도의 저장소가 필요없다. 서버는 인증만 처리하면 된다.
- 토큰은 개인 비밀키에 따라 관리되므로, 위변조의 영향을 적게 받는다. 다만, Claim에는 노출되어도 무방한 정보만을 권장한다.
- 단점
- 두 번째 장점과 연관된 부분으로, Claim이 노출될 가능성이 있어 중요한 정보는 payload에 담을 수 없다.
- Claim 크기에 따라 토큰의 길이가 길어지므로, 정보량이 많아질수록 토큰의 용량과 트래픽 속도에 영향을 미친다.
- JWT 토큰은 유효기간이 만료될 때 까지 사용이 가능하므로, 기한내 탈취되어 악용될 가능성이 있다.
토큰은 비밀키나, 유효성 검사(관리자, 사용자 구분?) 등 다뤄야할 부분이 매우 많다.
추후, admin 페이지 제작이나, 장바구니 페이지 구현(회원만 사용가능)을 할 경우, 토큰인증 부분을 공부해서 추가 포스팅을 해야겠다.
위코드 인증/인가 세션에서 비밀번호 암호화나 회원 토큰발행을 막연하게 들었고, 사실 프로젝트도 백엔드에서 위임을 했었다.
이를 직접 공부해보고, 내 서버의 로그인/회원가입 로직에서 암호화된 비밀번호도 저장해보고 로그인된 회원에 대한 response에 토큰을 첨부해보니 막상 재밌기도 하면서 뿌듯했다.😃😃
단촐하지만, Full-Stack의 꿈에 한발짝 다가간 것 같아 설레면서도, 백엔드 역시 만만치 않은 영역임을 조금이나마 느꼈다.!
내일은, 동적 라우팅과 페이지네이션을 위한 라우팅과 URL 파라미터에 대해 포스팅하겠다.
[출처]
- npm 공식문서(bcrypt) : www.npmjs.com/package/bcrypt
- sungjun-jin 님의 블로그 : velog.io/@sungjun-jin/bcrypt
- npm 공식문서(jsonwebtoken) : www.npmjs.com/package/jsonwebtoken?activeTab=readme
- ING-YEO 님의 블로그 : ing-yeo.net/2020/02/study-nodejs-jwt-authorization/
반응형'Back-End > Express.js' 카테고리의 다른 글
[Express.js] 미들웨어(Middleware) (0) 2021.02.15 [Express.js] CORS(Cross-Origin Resource Sharing) (0) 2021.02.13 [Express.js] Query String, Semantic URL (0) 2021.02.07 [Express.js] Routing(express.Router()) (0) 2021.02.02 [Express.js] Express 설치 및 실행 (0) 2021.02.01