- vừa được xem lúc

[Authentication] Xác thực với JWT cùng mã hóa Đối xứng và Bất đối xứng (Phần 2)

0 0 26

Người đăng: Phạm Đình Thiện

Theo Viblo Asia

Trong bài trước, chúng ta đã cùng tìm hiểu về các kiến thức cơ bản và cơ chế hoạt động của JWT. Trong bài này, anh em mình sẽ tiếp tục cùng nhau tìm hiểu về một phần mà mình nghĩ là hay nhất khi đề cập đến JWT, đó là áp dụng Mã hóa đối xứng và Mã hóa bất đối xứng trong xác thực JWT.

Mình sẽ sử dụng NodeJSMongoDB để triển khai việc mã hóa cho JWT. Còn về ý tưởng, cách thức áp dụng thì anh em dùng ngôn ngữ lập trình nào cũng chơi được hết nhé.

Xác thực JWT cùng Mã hóa đối xứng và Mã hóa bất đối xứng

1. Xác thực JWT cùng Mã hóa đối xứng

Mã hóa đối xứng là phương thức mã hóa dữ liệu sử dụng cùng một key vừa để mã hóa và vừa để giải mã thông tin.
Cách làm này chắc không còn xa lạ gì với anh em khi ban đầu tiếp xúc với JWT.

Cách triển khai cơ bản như sau:

z4224827978942_f37dc2546f9e320d7d8b34ba37bbef00.jpg

Đối với cách triển khai này, chúng ta tạo một JWT với một payload chứa thông tin về userid và email.

  1. Chúng ta sử dụng phương thức jwt.sign() để mã hóa thông tin này với key secretKey và thời gian sống của token là 1 giờ.
  2. Sau đó, chúng ta sử dụng phương thức jwt.verify() để xác thực JWT với key secretKey và lấy thông tin.

Anh em tham khảo source code GIT ở đây nhé.

B1: Import thư viện và khởi tạo secretKey

// jwt.aes.js
// Import thư viện và khởi tạo secretKey
const jwt = require('jsonwebtoken');
const secretKey = 'mysupersecretkey';

B2: Tạo hàm tạo JWT

// Hàm tạo JWT
const genToken = (userInfo) => { const accessToken = jwt.sign({ userid: userInfo._id, email: userInfo.email }, secretKey, { expiresIn: '1h' }); return accessToken;
}

B3: Tạo hàm xác thực JWT

// Hàm xác thực JWT
const validateToken = async(accessToken) => { jwt.verify(accessToken, secretKey, (err, decode) => { if(err) { console.error('error verify token'); } else{ console.log('decode jwt::', decode); } });
}

B4: Kiểm tra kết quả

// Thực thi
async function runScript() { const userId = 1; const accessToken = genToken({_id: userId, email: 'pdthien@gmail.com'}); await validateToken(accessToken);
}
runScript();
  • Ưu điểm:
    • Dễ thực hiện vì sử dụng 1 key duy nhất để vừa mã hóa và giải mã thông tin.
    • Giảm áp lực cho phía server vì không phải lưu thêm dữ liệu gì cho việc xác thực.
  • Nhược điểm: Không an toàn vì khi hacker ăn cắp được key đó, sẽ dễ dàng tạo ra các fake JWT, có thể truy cập tấn công ứng dụng.

2. Xác thực JWT cùng Mã hóa bất đối xứng

Mã hóa bất đối xứng sử dụng 2 key khác nhau (public keyprivate key) để mã hóa và giải mã thông tin. Trong đó, public key được chia sẻ với mọi người và private key được giữ bí mật.

  • Private key được sử dụng để mã hóa (sign) thông tin tạo ra JWT.
  • Public key được sử dụng để giải mã (verify) JWT đó, không có chiều ngược lại. Nên dù hacker có lấy được Public key cũng không thể tạo được fake JWT truy cập được ứng dụng.

Cách thức triển khai như sau:

feef4e0f664dba13e35c.jpg

Đối với cách triển khai này, chúng ta tạo một JWT với một payload chứa thông tin về userid và email

  1. Chúng ta sử dụng MongoDB để tạo bảng KeyToken lưu userId và publicKey.
  2. Tạo 2 key privateKeypublicKey sử dụng thuật toán rsa thông qua thư viện crypto.
  3. Chúng ta sử dụng phương thức jwt.sign() để mã hóa thông tin này với key privateKey và thời gian sống của token là 1 giờ.
  4. Chúng ta sử dụng phương thức jwt.verify() để xác thực JWT với key publicKey và lấy thông tin.

Anh em tham khảo source code GIT ở đây nhé.

B1: Khởi tạo kết nối MongoDB

// init.mongodb.js
const mongoose = require("mongoose");
const connnectString = 'mongodb://127.0.0.1:27017/jwt-auth'; mongoose.connect(connnectString)
.then(_ => console.log('mongoDB connected'))
.catch(err => console.log('mongDB error ' + err)); module.exports = mongoose;

B2: Tạo bảng (collection) Key lưu trữ Public key

// keytoken.model.js
const { Schema, model } = require('mongoose'); const DOCUMENT_NAME = 'Key';
const COLLECTION_NAME = 'Keys'; var keyTokenSchema = new Schema({ userid: { type: Number, required: true }, publicKey: { type: String, require: true }
}, { timestamps: true, collection: COLLECTION_NAME
}); module.exports = model(DOCUMENT_NAME, keyTokenSchema);

B3: Tạo hàm genToken. Trong hàm xử lý 2 việc: Lưu public key vào DB + Dùng privateKey để mã hóa tạo JWT

// jwt.rsa.js
// Import thư viện và các biến
const jwt = require('jsonwebtoken');
const crypto = require('crypto');
require('./init.mongodb');
const keytokenModel = require("./keytoken.model.js");
// Hàm xử lý lưu public key vào DB
const createKeyToken = async({userid, publicKey}) => { const publicKeyString = publicKey.toString(); const filter = {userid: userid}; const update = {userid: userid, publicKey: publicKeyString}; const options = {upsert: true, new: true}; await keytokenModel.findOneAndUpdate(filter, update, options);
}
// Hàm tạo JWT từ private key
const createAccessToken = async (payload, privateKey) => { const accessToken = await jwt.sign(payload, privateKey, { algorithm: 'RS256', expiresIn: '1h' }); return accessToken;
}
// Hàm genToken. Xử lý 2 việc: Lưu public key vào DB + Dùng private key để mã hóa tạo JWT.
const genToken = async(userInfo) => { // Dùng rsa để tạo privateKey và publicKey const { privateKey, publicKey } = crypto.generateKeyPairSync('rsa', { modulusLength: 4096, publicKeyEncoding: { type: 'pkcs1', format: 'pem' }, privateKeyEncoding: { type: 'pkcs1', format: 'pem' } }) // Lưu userid và publicKey vào bảng KeyToken await createKeyToken({ userid: userInfo._id, publicKey }) // Tạo accessToken với privateKey const accessToken = await createAccessToken({ userid: userInfo._id, email: userInfo.email }, privateKey); return accessToken;
}

B4: Tạo hàm validateToken xử lý xác thực JWT: Lấy publicKey từ DB + Xác thực (verify) token từ publicKey.

// Hàm lấy publicKey
const getPublicKey = async(userid) => { const filter = {userid: userid}; const token = await keytokenModel.findOne(filter); return token.publicKey;
}
// Hàm validateToken. Xử lý 2 việc: Lấy publicKey từ DB + Xác thực (verify) token từ publicKey
const validateToken = async(accessToken, userID) => { // Lấy publicKey trong DB const publicKeyString = await getPublicKey(userID); // Convert publicKey từ dạng string về dạng rsa có thể đọc được const publicKeyObject = crypto.createPublicKey(publicKeyString); // xác thực accessToken sử dụng publicKey jwt.verify(accessToken, publicKeyObject, (err, decode) => { if(err) { console.error('error verify token'); } else{ console.log('decode jwt::', decode); } });
}

B5: Kiểm tra kết quả

// Thực thi
async function runScript() { const userId = 1; let accessToken = await genToken({_id: userId, email: 'pdthien@gmail.com'}); await validateToken(accessToken, userId);
};
  • Nhược điểm: Phải lưu thêm Public key vào DB và truy xuất tới DB để lấy Public key khi cần xác thực, dẫn tới hiệu năng có thể bị ảnh hưởng. Tuy nhiên, ta hoàn toàn có thể cải thiện hiệu năng bằng cách sử dụng Cache như MemoryCache hoặc RedisCache.
  • Ưu điểm:
    • Tuy làm gia tăng áp lực cho server khi phải lưu thêm Public key vào DB và lấy ra sử dụng khi cần xác thực, nhưng nó giúp cho hệ thống của chúng ta gia tăng bảo mật hơn rất nhiều.
      (Vì như mình đã nói ở trên, dù hacker có đọc được thông tin Public key từ DB, cũng không thể tạo được fake JWT để truy cập ứng dụng do Private key chỉ được dùng để mã hóa (sign) thông tin tạo JWT, còn Public key chỉ được dùng để giải mã JWT để lấy thông tin, không có chiều ngược lại)
    • Giải quyết được một số nhược điểm của JWT như phần 1 mình đã chỉ ra cho anh em:
      1. Về vấn đề về Rủi ro bảo mật, thì khi áp dụng phương pháp mã hóa bất đối xứng, ta hoàn toàn có thể yên tâm vì độ bảo mật đã được nâng cao hơn rất nhiều (mình đã giải thích ở trên).
      2. Đối với vấn đề Không thể hủy bỏ token và Không hỗ trợ quản lý phiên, thì khi áp dụng phương pháp này, do chúng đã ta lưu publicKey trong DB, nên để hủy bỏ token hay hủy phiên đăng nhập của người dùng một cách đơn giản, ta chỉ việc xóa publicKey đó khỏi DB.

Anh em tham khảo source code GIT ở đây nhé.

Kết

Tổng kết lại một chút, khi anh em áp dụng mã hoá đối xứng vào JWT thì sẽ dễ thực hiện hơn và server không cần phải lưu thêm thông tin gì cho việc xác thực, qua đó làm tăng hiệu năng, tuy nhiên lại giảm bảo mật vì hacker chỉ cần lấy được sercetKey là có thể fake JWT để truy cập hệ thống. Còn khi áp dụng mã hoá bất đối xứng, ta cần phải lưu thêm publicKey vào DB, tuy có thể làm giảm hiệu năng một chút nhưng bù lại nó giúp ta gia tăng bảo mật hơn rất nhiều.

Nếu anh em có góp ý hay thảo luận thì comment ngay dưới bài viết nhé. Tks all !!!


Nguồn tham khảo:

  1. https://www.youtube.com/watch?v=pK3oBX0vB38

Bình luận

Bài viết tương tự

- vừa được xem lúc

Nơi đẹp nhất chính là nơi phù hợp nhất và câu chuyện giữa Anh Chàng NodeJS và Cô Nàng V8 ?

Kí Ức Đọng Về. Xin chào, lại là mình đây, sau gần 3 tuần vắng bóng với những chồng công việc không hồi kết, hôm nay mình cũng dành ra được thời gian để tiếp tục quay lại với Series NodeJS và những câu chuyện tối ưu Performance.

0 0 29

- vừa được xem lúc

NodeJS có thực sự nhanh như bạn nghĩ? ?

NodeJS dưới ánh mắt người đời. Có nhiều bạn đặt câu hỏi với mình quanh về vấn đề Hiệu Năng của NodeJS, chẳng hạn như:.

0 0 31

- vừa được xem lúc

[Nodejs thực chiến] Dockerize, Containerize nodejs app thật chuẩn

1. Đặt vấn đề:.

0 0 43

- vừa được xem lúc

Thật ngớ ngẩn khi mình dev NodeJs mãi một năm mới biết đến Microtask và Macrotask 💻🐸

Lời nói đầu. -----Xin chào các bạn mình tên là Vinh, hiện tại đang là một lập trình viên Nodejs.

0 0 26

- vừa được xem lúc

Hiều về kiến trúc hướng sự kiện của Node.js

Bài viết được dịch từ nguồn. Hầu hết các node objects như HTTP request, HTTP response hay HTTP stream - đều implement EventEmitter module nên chúng đều có thể:.

0 0 33

- vừa được xem lúc

Tổng hợp 10 thư viện và framework Node.js đang được Trending để nâng cao khả năng phát triển web của bạn

Bạn đang muốn cải thiện kiến thức về phát triển Node.js? mình xin được đã tổng hợp danh sách 10 thư viện và khung công cụ Node.

0 0 22