JWT Là Gì Và Cách Giải Mã Nó An Toàn
JSON Web Tokens xuất hiện ở khắp nơi trong phát triển web hiện đại — các header xác thực, các luồng OAuth, các khóa API, quản lý phiên. Nếu bạn đã làm việc với bất kỳ API nào yêu cầu một token Bearer, bạn đã dùng một JWT. Nhưng hầu hết các nhà phát triển đối xử với chúng như các khối mờ và hiếm khi nhìn vào bên trong. Hiểu cấu trúc của một JWT, mỗi phần có nghĩa là gì, và cách giải mã một cái mà không mắc các sai lầm bảo mật mất khoảng mười lăm phút. Hướng dẫn này bao quát tất cả.
Giải Phẫu Của Một JWT
Mỗi JWT là một chuỗi của ba đoạn được mã hóa Base64URL được phân tách bằng các dấu chấm:
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyXzEyMyIsImlzcyI6Imh0dHBzOi8vYXV0aC5leGFtcGxlLmNvbSIsImF1ZCI6Imh0dHBzOi8vYXBpLmV4YW1wbGUuY29tIiwiZXhwIjoxNzQ3MDAwMDAwLCJpYXQiOjE3NDY5OTY0MDAsInNjb3BlIjoicmVhZDp1c2VycyJ9.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c Định dạng là header.payload.signature. Mỗi đoạn mã hóa thông tin khác nhau.
Header
Đoạn đầu tiên là header. Được giải mã, ví dụ ở trên trở thành:
{
"alg": "RS256",
"typ": "JWT"
} alg chỉ định thuật toán được dùng để ký token — RS256 có nghĩa là RSA với SHA-256. typ xác định loại token. Hai trường này là tối thiểu; một số token cũng bao gồm kid (ID khóa) để cho biết verifier nào dùng public key khi nhiều khóa đang luân chuyển.
Payload
Đoạn thứ hai là payload — dữ liệu thực tế. Được giải mã:
{
"sub": "user_123",
"iss": "https://auth.example.com",
"aud": "https://api.example.com",
"exp": 1747000000,
"iat": 1746996400,
"scope": "read:users"
} Payload chứa các xác nhận: các tuyên bố về thực thể mà token đại diện. Một số xác nhận được chuẩn hóa (gọi là các xác nhận được đăng ký); các xác nhận khác đặc thù cho ứng dụng (gọi là các xác nhận riêng tư). Payload được mã hóa Base64URL, không được mã hóa bảo mật. Bất kỳ ai có chuỗi token đều có thể đọc mọi xác nhận bên trong nó.
Chữ Ký
Đoạn thứ ba là chữ ký. Nó được tạo bằng cách lấy header và payload được mã hóa, nối chúng với một dấu chấm, sau đó ký kết quả với khóa bí mật hoặc private key được chỉ định trong alg:
signature = sign(
base64url(header) + "." + base64url(payload),
secretOrPrivateKey
) Chữ ký cho phép bất kỳ bên nào có public key tương ứng (hoặc khóa bí mật, cho các thuật toán đối xứng) xác minh rằng token được phát hành bởi một nguồn đáng tin cậy và rằng cả header lẫn payload không bị sửa đổi sau khi phát hành. Một ký tự thay đổi duy nhất trong payload làm vô hiệu chữ ký hoàn toàn.
Cách Giải Mã Một JWT
Giải mã khác với xác minh. Giải mã chỉ đơn giản đọc dữ liệu bên trong một token. Xác minh xác nhận token là xác thực và chưa hết hạn. Bạn luôn nên xác minh trong code production; giải mã một mình hữu ích cho gỡ lỗi.
Bước 1: Chia token
Chia chuỗi JWT trên ký tự .. Bạn sẽ nhận được ba đoạn. Nếu bạn nhận được ít hơn ba, token bị méo.
Bước 2: Giải mã header và payload
Base64URL giải mã mỗi đoạn trong hai đoạn đầu tiên. Base64URL giống Base64 tiêu chuẩn nhưng dùng - thay vì + và _ thay vì /, không có các ký tự padding. Khi được giải mã, bạn có thể phân tích mỗi đoạn dưới dạng JSON.
Bước 3: Phân tích và kiểm tra
Đọc các xác nhận. Kiểm tra exp (hết hạn) đối với dấu thời gian Unix hiện tại. Xem iss và aud để xác nhận token được dự định cho ứng dụng của bạn. Đừng tin bất kỳ xác nhận nào cho đến khi bạn đã xác minh chữ ký.
Để kiểm tra nhanh trong quá trình phát triển, dùng JWT Decoder. Nó chạy hoàn toàn trong trình duyệt của bạn — token của bạn không bao giờ rời thiết bị. Bạn cũng có thể giải mã các đoạn base64 thủ công với Base64 encoder/decoder nếu bạn muốn xem các byte thô. Để xác minh chữ ký trong các script kiểm tra, HMAC generator cho phép bạn tái tạo các chữ ký HS256 mà không viết code.
Bước 4: Xác minh chữ ký trong code
Trong production, luôn xác minh chữ ký dùng một thư viện được xây dựng cho ngôn ngữ của bạn. Đừng triển khai xác minh chữ ký bằng tay. Các tùy chọn phổ biến bao gồm jose hoặc jsonwebtoken cho Node.js, PyJWT cho Python, golang-jwt/jwt cho Go và nimbus-jose-jwt cho Java.
Luôn truyền thuật toán một cách rõ ràng. Không bao giờ để thư viện suy ra thuật toán từ header của token.
Các Xác Nhận Tiêu Chuẩn (Các Xác Nhận Được Đăng Ký)
RFC 7519 định nghĩa một bộ các tên xác nhận được đăng ký. Chúng không bắt buộc, nhưng khi có mặt chúng phải tuân theo ngữ nghĩa tiêu chuẩn.
iss — Người Phát Hành
Thực thể đã tạo và ký token. Thường là một URL xác định máy chủ ủy quyền (ví dụ, https://auth.example.com). Các verifier nên kiểm tra rằng iss khớp với một giá trị mong đợi và từ chối các token từ các người phát hành không xác định.
sub — Chủ Thể
Đối tượng mà token đại diện — thường là một ID người dùng, tên tài khoản dịch vụ, hoặc định danh thiết bị. Giá trị phải duy nhất trong ngữ cảnh của người phát hành. Ứng dụng của bạn dùng sub để xác định người dùng nào mà yêu cầu thuộc về.
aud — Khán Giả
(Những) người nhận dự định của token. Nếu định danh API của bạn là https://api.example.com, các token được phát hành cho các dịch vụ khác nên bị từ chối. Thất bại trong việc xác thực aud cho phép những kẻ tấn công tái sử dụng một token thu được từ một dịch vụ tại một dịch vụ khác.
exp — Thời Gian Hết Hạn
Một dấu thời gian Unix sau đó token không được chấp nhận. Luôn xác thực exp. Một token không có hết hạn thực tế tồn tại mãi mãi — nếu nó bị đánh cắp, kẻ tấn công có truy cập vô thời hạn.
iat — Được Phát Hành Lúc
Dấu thời gian Unix khi token được tạo. Hữu ích để phát hiện các token chưa hết hạn về mặt kỹ thuật nhưng cũ một cách đáng ngờ. Bạn có thể dùng iat để áp đặt một tuổi token tối đa độc lập với exp.
nbf — Không Trước
Dấu thời gian Unix trước đó token không được chấp nhận. Ít phổ biến hơn exp, nhưng hữu ích khi phát hành các token trước — ví dụ, một tác vụ được lên lịch không nên bắt đầu cho đến một thời điểm cụ thể.
jti — ID JWT
Một định danh duy nhất cho token. Cho phép những người phát hành ngăn ngừa các cuộc tấn công replay token bằng cách lưu trữ các giá trị jti đã được dùng và từ chối các token với các ID trùng lặp. Cần thiết khi bạn cần các token một lần dùng, chẳng hạn các liên kết đặt lại mật khẩu.
Các Bẫy Bảo Mật
Các triển khai JWT có một lịch sử của các lỗ hổng tinh tế. Đây là các điều quan trọng nhất để hiểu trước khi ship code chấp nhận các JWT.
Cuộc Tấn Công "alg: none"
Một số thư viện JWT từ những ngày đầu của tiêu chuẩn sẽ chấp nhận một token chỉ định "alg": "none" trong header của nó. Một kẻ tấn công có thể lấy bất kỳ token hợp lệ nào, thay thuật toán bằng none, loại bỏ chữ ký, và thư viện sẽ chấp nhận nó là hoàn toàn hợp lệ — không yêu cầu chữ ký.
Khắc phục: luôn chỉ định thuật toán mong đợi một cách rõ ràng khi gọi hàm xác minh của bạn. Đối xử với bất kỳ token nào tuyên bố alg: none là không hợp lệ. Hầu hết các thư viện hiện đại đã giải quyết điều này, nhưng đáng để xác nhận các mặc định thư viện của bạn trước khi vào production.
Nhầm Lẫn Thuật Toán (Hạ Cấp RS256 Xuống HS256)
Một số thư viện hỗ trợ cả hai thuật toán sẽ dùng trường alg trong header của token để quyết định cách xác minh. Một kẻ tấn công có thể thay đổi thuật toán thành HS256, sau đó ký token dùng public key của máy chủ làm bí mật HMAC. Máy chủ, thấy HS256, sẽ xác minh đối với public key và chấp nhận token giả mạo.
Khắc phục là giống nhau: khóa thuật toán trong code xác minh của bạn. Không bao giờ để header của token ảnh hưởng đến thuật toán nào được dùng cho xác minh.
Chấp Nhận Các Token Đã Hết Hạn
Không xác thực exp là một sự bỏ sót phổ biến — đôi khi được giới thiệu khi các nhà phát triển thêm một thời gian gia hạn phát triển mà không có giới hạn, hoặc khi đường dẫn code xác thực bị bỏ qua trong một nhánh code. Đối xử với một token đã hết hạn giống như một token bị thiếu: từ chối nó với một 401 và yêu cầu client xác thực lại hoặc dùng một token làm mới.
Dữ Liệu Nhạy Cảm Trong Payload
Payload được mã hóa Base64URL, không được mã hóa bảo mật. Bất kỳ ai chặn token đều có thể giải mã nó. Đừng đặt mật khẩu, số thẻ tín dụng, số an sinh xã hội, hoặc các dữ liệu nhạy cảm khác trong payload. Nếu bạn cần truyền các xác nhận nhạy cảm an toàn, dùng một token JWE (JSON Web Encryption), mã hóa payload. Các JWT được ký với JWS (trường hợp phổ biến) chỉ đảm bảo tính xác thực, không phải tính bảo mật.
Thiếu Xác Thực Khán Giả
Bỏ qua xác thực aud có nghĩa là một token được phát hành cho Dịch vụ A có thể được replay tại Dịch vụ B — miễn là cả hai dịch vụ chia sẻ cùng khóa ký hoặc tin tưởng cùng người phát hành. Trong các kiến trúc đa dịch vụ, luôn xác thực rằng khán giả của token khớp với định danh của dịch vụ của bạn.
Các Thực Hành Tốt Nhất Cho 2026
Chọn RS256 Cho Hầu Hết Các Ứng Dụng
RS256 dùng một private key để ký và một public key để xác minh. Chỉ máy chủ ủy quyền giữ private key. Bất kỳ dịch vụ nào cũng có thể xác minh các token dùng public key, có thể được xuất bản công khai (thường qua một endpoint JWKS). Nếu bất kỳ dịch vụ cá nhân nào bị xâm phạm, những kẻ tấn công có được khả năng xác minh các token — nhưng không phải để giả mạo các token mới.
HS256 dùng một bí mật chia sẻ duy nhất cho cả ký và xác minh. Bất kỳ dịch vụ nào có thể xác minh các token cũng có thể tạo chúng. Trong một thiết lập microservice, chia sẻ bí mật qua nhiều dịch vụ tăng bán kính nổ của một vi phạm.
Giữ Các Token Truy Cập Có Thời Gian Sống Ngắn
Không có cơ chế thu hồi tích hợp cho các JWT — một khi được phát hành, một token hợp lệ cho đến khi nó hết hạn (trừ khi bạn triển khai một blocklist, đưa lại trạng thái phía máy chủ). Các thời gian hết hạn ngắn (15 phút đến 1 giờ) giới hạn cửa sổ thiệt hại nếu một token bị đánh cắp. Ghép các token truy cập với các token làm mới có thời gian sống dài được lưu trữ trong các cookie HttpOnly an toàn, không phải trong localStorage.
Dùng Xoay Vòng Token Làm Mới
Khi một client dùng một token làm mới để lấy một token truy cập mới, phát hành một token làm mới mới và vô hiệu hóa cái cũ. Theo cách này, một token làm mới bị đánh cắp được phát hiện lần tiếp theo client hợp pháp cố dùng cái gốc: máy chủ thấy một token làm mới được tái sử dụng và có thể thu hồi toàn bộ phiên. Mẫu này được mô tả trong RFC 6819 và được hỗ trợ rộng rãi bởi các máy chủ ủy quyền hiện đại.
Xuất Bản Một Endpoint JWKS
Một endpoint JSON Web Key Set (JWKS) (/.well-known/jwks.json theo quy ước) xuất bản các public key ký của bạn trong một định dạng tiêu chuẩn. Các dịch vụ khác có thể lấy JWKS và xác minh các token mà không được trao các khóa ngoài băng. Điều này cũng làm cho việc xoay vòng khóa đơn giản: thêm khóa mới vào JWKS, bắt đầu ký với nó, sau đó loại bỏ khóa cũ một khi tất cả các token hiện có đã hết hạn.
Xoay Vòng Các Khóa Định Kỳ
Ngay cả khi private key của bạn không bao giờ bị xâm phạm, xoay vòng các khóa định kỳ giới hạn cửa sổ trong đó một khóa bị đánh cắp có thể được dùng để giả mạo các token. Dùng kid trong các header JWT của bạn để tham chiếu khóa ký, để các verifier có thể chọn public key đúng từ JWKS của bạn mà không phá vỡ các token hiện có trong một xoay vòng.
Xác Thực Mọi Xác Nhận
Kiểm tra iss, aud, exp và nbf trên mọi yêu cầu. Đừng bỏ qua bất kỳ cái nào trong số này vì chúng có vẻ dư thừa trong thiết lập của bạn — các điều kiện làm cho chúng có vẻ không cần thiết hôm nay chính xác là các điều kiện thay đổi trong một sự cố.
JWT vs. Cookie Phiên
Các JWT và các phiên phía máy chủ giải quyết cùng vấn đề — duy trì trạng thái xác thực qua HTTP không trạng thái — nhưng với các đánh đổi khác nhau.
Các JWT tự chứa. Máy chủ không cần truy vấn một cơ sở dữ liệu để xác thực một yêu cầu. Điều này làm chúng hấp dẫn cho các hệ thống phân tán và các API được tiêu thụ bởi các client mobile hoặc bên thứ ba. Nhược điểm là thu hồi yêu cầu một blocklist (đưa lại trạng thái phía máy chủ) hoặc chấp nhận thời gian sống còn lại của một token bị xâm phạm.
Các cookie phiên được máy chủ xác thực. Máy chủ lưu trữ trạng thái phiên và có thể thu hồi truy cập ngay lập tức bằng cách xóa phiên. Các cookie với các cờ HttpOnly và Secure được bảo vệ khỏi truy cập JavaScript và chặn mạng. Nhược điểm là mỗi yêu cầu yêu cầu một tra cứu cơ sở dữ liệu, có thể trở thành nút thắt cổ chai ở quy mô.
Đối với các ứng dụng web truyền thống với các trang được render phía máy chủ và một backend duy nhất, các cookie phiên vẫn là một lựa chọn vững chắc và đơn giản hơn. Đối với các API được tiêu thụ bởi nhiều client, các kiến trúc microservice và các ứng dụng mobile, các JWT với hết hạn ngắn và xoay vòng token làm mới là tùy chọn thực tế hơn.
Giải Mã Token Của Bạn
Cách nhanh nhất để kiểm tra một JWT là dán nó vào JWT Decoder. Nó chia token, giải mã header và payload, và render các xác nhận trong một định dạng dễ đọc — tất cả mà không gửi token của bạn đi đâu cả. Nếu bạn cần mã hóa hoặc giải mã các chuỗi Base64URL thô thủ công, Base64 encoder/decoder xử lý cả các biến thể tiêu chuẩn và an toàn URL. Để xác minh các chữ ký HMAC trong các script kiểm tra, HMAC generator cho phép bạn tái tạo một chữ ký HS256 và so sánh nó với cái token của bạn chứa.
Đối với đặc tả đầy đủ, xem RFC 7519 (JWT) và các ví dụ tương tác tại jwt.io.