Ngoảnh đi ngoảnh lại cái lại hết một năm rồi đấy các bác ạ. Năm vừa rồi các bác đã làm được gì rồi, em thì vẫn lôm côm lắm, chẳng ra đâu vào với đâu cả. Thôi không lan man dài dòng nữa vào luôn chủ đề nhé.
Chắc các bạn đã nghe tới JWT rồi chứ, cứ nghe đến cái này thì mình liên tưởng đến xác thực một cái gì đó, hôm nay rảnh háng, ngồi đọc nghiên cứu thêm xem có vài ký tự lằng nhằng trông thế thôi mà bảo mật phết các bác ạ.
Đối với bất kỳ một ứng dụng web, di động, desktop…vv chắc chắn các bạn đều đã từng tạo tài khoản, sau đó phải đăng nhập để sử dụng các tính năng bên trong của ứng dụng, hành động đó gọi là Authentication – xác thực người dùng.
Vậy thì xác thực người dùng bằng cách nào?

Mình copy cái ảnh này nhé. Cảm ơn tác giả 😀 Thường thì chúng ta vẫn làm như này đúng không nào, ok, chẳng vấn đề gì 😀 Cơ mà giờ mà sau này chúng ta phát triển thêm cho các Mobile Apps, TV Apps,… thì chẳng lẽ lại dùng cách trên @@ cơ mà có phải trình duyệt đâu mà có Session với Cookie.
Toang rồi, giờ phải làm sao. Để giải quyết được vấn đề trên thì JWT đã được ra đời.
JWT là gì ?
JSON Web Token (JWT) là 1 tiêu chuẩn mở (RFC 7519 bạn nào tốt ngoại ngữ thì đọc ở đây nhé. Bách khoa toàn thư, đọc chả hiểu gì @@) định nghĩa cách thức truyền tin an toàn giữa các ứng dụng với nhau(web, app, tv,…) bằng 1 đối tượng JSON. Thông tin này có thể được xác thực và đánh dấu tin cậy nhờ vào “chữ ký” của nó. Phần chữ ký của JWT sẽ được mã hóa lại bằng HMAC hoặc RSA.

Ví dụ đây sẽ là một chuỗi JWT: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJ0b3B0YWwuY29tIiwiZXhwIjoxNDI2NDIwODAwLCJodHRwOi8vdG9wdGFsLmNvbS9qd3RfY2xhaW1zL2lzX2FkbWluIjp0cnVlLCJjb21wYW55IjoiVG9wdGFsIiwiYXdlc29tZSI6dHJ1ZX0.yRQYnWzskCZUxPwaQupWkiUzKELZ49eM7oWxAQK_ZXw
Đó các bạn nhìn thấy chưa, chả hiểu gì 😀 cơ mà để ý kỹ nhé. Có 2 dấu chấm xuất hiện trong chuỗi này. Đó chính là cấu trúc của JWT. JSON Web Token bao gồm 3 phần, được ngăn cách nhau bởi dấu chấm (.):
-
Header
-
Payload
-
Signature
<base64-encoded header>.<base64-encoded payload>.<base64-encoded signature>
Cùng tìm hiểu sâu hơn xem bên trong 3 cái này gồm những gì nhé.
-
Header: bao gồm hai phần chính: loại TOKEN(mặc định là JWT ngoài ra còn JWS hay JWE nữa cơ, xịn sò lắm) và ALGORITHM là thuật toán mã hóa: ở đây có rất nhiều: HS256, HS384, HS512, RS256, RS384, RS512, ES256, ES384, ES512, PS256, PS384.
Ví dụ cái cho dễ hiểu nhé:
{
"alg": "HS256",
"typ": "JWT"
}
Đơn giản nhỉ, nó chỉ là JSON object thôi mà bao gồm key: value. Ở ví dụ trên HS256 là thuật toán có tên HMAC-SHA256, một thuật toán băm sử dụng khóa bí mật (Secret Key) để tính toán tạo ra chữ ký.
Giờ mình sẽ Encode Base64URL cái này. Không phải Base64 mà các bạn vẫn hay dùng đâu, khác ở đây là:
-
Không thêm = vào
-
Các ký tự + và / sẽ được thay thế bằng – và _
Mình viết bằng NodeJS sử dụng thư viện CryptoJS nhé:
var CryptoJS = require("crypto-js");
function base64Url(source) {
// Encode in classical base64
var encryptedWord = CryptoJS.enc.Utf8.parse(source);
var encodedSource = CryptoJS.enc.Base64.stringify(encryptedWord);
// Remove padding equal characters
encodedSource = encodedSource.replace(/=+$/, '');
// Replace characters according to base64url specifications
encodedSource = encodedSource.replace(/\+/g, '-');
encodedSource = encodedSource.replace(/\//g, '_');
return encodedSource;
}
console.log(base64Url(JSON.stringify({"alg": "HS256", "typ": "JWT"})));
Kết quả: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
-
Payload: nơi chứa các nội dung của thông tin (claim). Thông tin truyền đi có thể là mô tả của 1 thực thể (ví dụ như người dùng) hoặc cũng có thể là các thông tin bổ sung thêm cho phần Header. Nhìn chung, chúng được chia làm 3 loại: reserved, public và private.
Reserved: là những thông tin đã được quy định ở trong IANA JSON Web Token Claims registry. Chúng bao gồm: Chú ý rằng các khóa của claim đều chỉ dài 3 ký tự vì mục đích giảm kích thước của Token.
-
iss (issuer): tổ chức phát hành token
-
sub (subject): chủ đề của token
-
aud (audience): đối tượng sử dụng token
-
exp (expired time): thời điểm token sẽ hết hạn
-
nbf (not before time): token sẽ chưa hợp lệ trước thời điểm này
-
iat (issued at): thời điểm token được phát hành, tính theo UNIX time
-
jti: JWT ID
Public: Khóa nên được quy định ở trong IANA JSON Web Token Registry, cái này không bắt buộc do mình tạo thôi
Private: Phần thông tin thêm dùng để truyền qua giữa các ứng dụng client.
Bonus cho cái ví dụ cho dễ hình dung nhé:
var payload = {
"jwi": 11111995,
"iss": "MegaAds Blog",
"sub": "JWT Implement",
"iat": 1580397974,
"exp": 1580484374,
"aud": ["web","app","smart-tv"],
"data": {
"id": "64hjg-54bfjyp-ebfjuzd-5k39-dsfjskljf43",
"username": "tuananhzippy",
"occupation": "Full Stack Web Developer",
},
"website": "http://blog.megaads.vn"
};
console.log(base64Url(JSON. stringify(payload)));
Kết quả: eyJqd2kiOjExMTExOTk1LCJpc3MiOiJNZWdhQWRzIEJsb2ciLCJzdWIiOiJKV1QgSW1wbGVtZW50IiwiaWF0IjoxNTgwMzk3OTc0LCJleHAiOjE1ODA0ODQzNzQsImF1ZCI6WyJ3ZWIiLCJhcHAiLCJzbWFydC10diJdLCJkYXRhIjp7ImlkIjoiNjRoamctNTRiZmp5cC1lYmZqdXpkLTVrMzktZHNmanNrbGpmNDMiLCJ1c2VybmFtZSI6InR1YW5hbmh6aXBweSIsIm9jY3VwYXRpb24iOiJGdWxsIFN0YWNrIFdlYiBEZXZlbG9wZXIifSwid2Vic2l0ZSI6Imh0dHA6Ly9ibG9nLm1lZ2FhZHMudm4ifQ
-
Signature: Phần chữ ký được tạo bằng cách kết hợp 2 phần Header + Payload, rồi mã hóa nó lại bằng 1 giải thuật mã hóa nào đó, càng phức tạp thì càng tốt, ví dụ như HMAC SHA-256. Cùng tìm hiểu một chút về HMAC nhé:
Đầu tiên ta phải hiểu MAC là gì: Message Authentication Code là một chuỗi các bit được gửi cùng với một tin nhắn(ở đây có thể là dữ liệu của chúng ta). MAC phụ thuộc vào chính thông điệp và khóa bí mật(mã hóa đối xứng). Không ai có thể tính toán MAC mà không biết khóa. Điều này cho phép hai người chia sẻ một khóa bí mật để gửi tin nhắn cho nhau mà không sợ người khác sẽ can thiệp vào tin nhắn. (Ít nhất, nếu ai đó không làm xáo trộn với một thông điệp, điều này có thể được phát hiện bằng cách kiểm tra để xem nếu MAC là đúng.)
HMAC là một công thức để biến các hàm băm (như MD5 hoặc SHA256) thành MAC. Vì vậy, HMAC-MD5 và HMAC-SHA256 là các thuật toán MAC cụ thể, giống như QuickSort là một thuật toán sắp xếp cụ thể.
Quay lại với JWT, giờ thì ta đem Header + “.” + Payload đi mã hóa là ta có một JWT hoàn chỉnh.
Để mã hóa ta cần chọn 1 Secret key. Khóa này chính là mấu chốt của vấn đề. Đừng để lộ cái này ra nhé, không là toang đấy
var secretKey = 'Il@vEy0U%123'; var token = encodeHeader + '.' + encodePayload; var signature = CryptoJS.HmacSHA256(token, secretKey); signature = base64Url(signature);
Sau khi mã hóa HMACSHA256 ta lại tiếp tục đem đi Base64 nó. Vậy là xong rồi đem đi nối vào phần còn lại như công thức Header + Payload + Signature;
var signedToken = token + '.' + signature;
console.log('Signed Token', signedToken);
Kết quả: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqd2kiOjExMTExOTk1LCJpc3MiOiJNZWdhQWRzIEJsb2ciLCJzdWIiOiJKV1QgSW1wbGVtZW50IiwiaWF0IjoxNTgwMzk3OTc0LCJleHAiOjE1ODA0ODQzNzQsImF1ZCI6WyJ3ZWIiLCJhcHAiLCJzbWFydC10diJdLCJkYXRhIjp7ImlkIjoiNjRoamctNTRiZmp5cC1lYmZqdXpkLTVrMktZHNmanNrbGpmNDMiLCJ1c2VybmFtZSI6InR1YW5hbmh6aXBweSIsIm9jY3VwYXRpb24iOiJGdWxsIFN0YWNrIFdlYiBEZXZlbG9wZXIifSwid2Vic2l0ZSI6Imh0dHA6Ly9ibG9nLm1lZ2FhZHMudm4ifQ.YThjMjczZDBjMDliYTgzYzYxMTJkMzM0MWIwNDlkMzJmYzcyNDVmNDYwZjQwNDk1ZDEyYWE4OThlYWRhYmIzMg
JWT bảo vệ dữ liệu của chúng ta bằng cách nào?
JWT không bảo vệ dữ liệu của bạn. JWT nó không ẩn, không làm mờ, không che giấu dữ liệu, mà nó được sử dụng để chứng minh rằng dữ liệu được tạo ra bởi một nguồn xác thực.
Các bạn có thể nhìn lại ở các bước xử lý Header, Payload, Signature trên kia, dữ liệu chỉ được Encoded và Hash (Signed) chứ không phải Encrypted. Úi có cái này mới này Encode va Encrypt tưởng nó giống nhau mà nhỉ, cơ mà không phải 😀
- Encoding transforms data into another format using a scheme that is publicly available so that it can easily be reversed. (tạm dịch là biến đổi dữ liệu sang một định dạng khác bằng cách sử dụng các sơ đồ có sẵn công khai, ai cũng có thể đọc và giải mã một cách dễ dàng)
- Encryption transforms data into another format in such a way that only specific individual(s) can reverse the transformation. (tạm dịch là biến đổi dữ liệu sang một định dạng khác theo cách mà chỉ cá nhân cụ thể mới có thể giải mã, người biết mới có thể giải mã)
- Nguồn: http://danielmiessler.com/study/encoding_vs_encryption/
Vậy thì có bạn sẽ đặt ra thắc mắc: “nếu một kẻ tấn công ở giữa (Man-in-the-middle) bắt được gói tin có chứa mã JWT rồi họ decode ra và lấy được thông tin của user thì sao?” 😀 đừng lo lắng vì đã có HTTPS.
Server xác thực mã JWT gửi lên từ client ra sao?
Trong phần 3 trên kia các bạn hãy nhìn lại cho mình đó là khi tạo mã JWT, chúng ta có sử dụng tới một chuỗi bí mật “Secret” trong bước tạo chữ ký (signature).
Chuỗi “Secret” này là unique cho ứng dụng và phải được ưu tiên lưu trữ bảo mật cẩn thận ở phía server. Khi nhận được mã Token gửi lên từ phía client, Server sẽ lấy phần Signature (chữ ký) bên trong mã token đó, và verify (kiểm tra) xem cái chữ ký nhận được có đúng chính xác là được HASH (băm) bởi cùng một thuật toán và chuỗi “Secret” như trên hay không.
Tổng kết lại thì bài viết vừa rồi mình có đề cập tới JWT trên khía cạnh lý thuyết và các thức thực hiện. Khi gặp bài toán cụ thể ta sẽ vận dụng nó theo nhiều cách khác nhau(accessToken, refeshToken). Nhưng túm lại thì JWT nó chỉ có thế 😀
Qua bài viết này mình mong muốn giúp cho những ai còn chưa hiểu nhiều về JWT hoặc chưa hiểu về nó thì có thể hình dung được phần nào những lợi ích mà nó đem lại cho Web Service.
Chúc các bạn thành công, xin chào và hẹn gặp lại!
Tham khảo:
- https://trungquandev.com/hieu-sau-ve-jwt-json-web-tokens/
- https://auth0.com/resources/ebooks/jwt-handbook
- https://techmaster.vn/posts/33959/khai-niem-ve-json-web-token









(5 lượt thả tim)



