Mọi API đều kiểm chứng danh tính trên từng request. Với JWT thì đó là một phép kiểm chữ ký mỗi lần, và bài này đo xem phép kiểm đó tốn bao nhiêu — câu trả lời phụ thuộc hoàn toàn vào một chữ trong tên thuật toán.
Bảo vệ route
JWTAuth auth = JWTAuth.create(vertx, new JWTAuthOptions()
.addPubSecKey(new PubSecKeyOptions()
.setAlgorithm("HS256")
.setBuffer("mot-chuoi-bi-mat-du-dai")));
router.get("/rieng")
.handler(JWTAuthHandler.create(auth)) // kiem chu ky + han dung
.handler(c -> c.response().end("ok"));
gọi không có token -> 401
gọi có token -> 200
JWTAuthHandler kiểm chữ ký, hạn dùng, rồi đặt c.user() cho các handler sau. Không hợp lệ thì nó tự trả 401 và không gọi next() — đúng luật của phần 9.
Chi phí mỗi request
500 kết nối đồng thời, endpoint không làm gì ngoài trả "ok":
| Endpoint | Thông lượng | p50 | So với không xác thực |
|---|---|---|---|
| Không xác thực | 99 113 req/s | 4,4 ms | — |
| JWT HS256 | 99 657 req/s | 4,6 ms | 100% |
| JWT RS256 | 27 486 req/s | 16,1 ms | 28% |
| JWT HS256 + kiểm vai trò | 99 034 req/s | 4,5 ms | 100% |
Lý do: HS256 là HMAC — một phép băm đối xứng, vài trăm nano giây. RS256 là chữ ký RSA 2048 bit, và kiểm chứng nó là một phép luỹ thừa mô-đun. Ở tốc độ gần trăm nghìn request mỗi giây, khác biệt đó chiếm trọn event loop.
Và nhớ phần 3: phép kiểm này chạy trên event loop. Nó không phải I/O nên executeBlocking không phải câu trả lời hiển nhiên — nhưng ở tải cao thì RSA chính là một dạng "chặn" bằng CPU.
Kiểm vai trò gần như miễn phí
router.get("/quan-tri")
.handler(JWTAuthHandler.create(auth))
.handler(c -> {
if (!"quan-tri".equals(c.user().principal().getString("vaiTro"))) { c.fail(403); return; }
c.response().end("ok");
});
99 034 req/s — bằng đúng mức chỉ xác thực. Hợp lý: thông tin vai trò đã nằm trong token và đã được giải mã lúc kiểm chữ ký; đọc thêm một trường JSON không tốn gì.
Đây cũng là lý lẽ chính của JWT: không phải "nhanh hơn session" nói chung, mà là không cần hỏi ai để biết người dùng là ai và được làm gì.
Chọn thuật toán
| Tình huống | Thuật toán |
|---|---|
| Bạn vừa phát hành vừa kiểm token (một hệ thống) | HS256 |
| Nhiều dịch vụ kiểm token do một nơi phát hành | RS256 — nhưng đọc tiếp |
| Thông lượng rất cao và bắt buộc RS256 | Cân nhắc ES256, hoặc cache kết quả kiểm |
Dòng giữa là tình huống thật của phần lớn kiến trúc microservice, và bảng đo ở trên là cái giá. RS256 tồn tại vì lý do chính đáng: dịch vụ kiểm token chỉ cần khoá công khai, nên lộ nó ra cũng không ai giả được token. Với HS256, mọi dịch vụ kiểm token đều giữ khoá bí mật — tức là mọi dịch vụ đều phát hành được token.
Đó là đánh đổi bảo mật, không phải đánh đổi hiệu năng, và bảo mật thường thắng. Nhưng hãy biết mình đang trả 72% thông lượng, và cân nhắc:
Kiểm một lần ở gateway. Gateway dùng RS256, các dịch vụ bên trong tin header đã được gateway xác minh. Bài 28 của sê-ri sẽ dựng gateway đó.
Token sống lâu hơn. Kiểm chữ ký là chi phí mỗi request; không có cách nào giảm số lần kiểm ngoài việc giảm số request.
Ba điều đừng quên
Luôn đặt hạn dùng. setExpiresInMinutes — thiếu nó thì một token rò rỉ có giá trị vĩnh viễn.
Đừng nhét dữ liệu lớn vào token. Token HS256 trong phép đo dài 183 ký tự, RS256 dài 482. Mọi request đều mang cả chuỗi đó qua mạng.
Đừng tin alg trong token. Cấu hình rõ thuật toán phía máy chủ; đây là lỗ hổng kinh điển của JWT khi thư viện chấp nhận alg: none.
Bài sau: đo Vert.x Web cạnh Spring MVC — và cạnh Spring MVC bật luồng ảo, thứ làm bảng so sánh khác hẳn.
Thử ba mươi giây
# xem trong token cua ban co gi (phan giua, khong can khoa)
echo "<token>" | cut -d. -f2 | base64 -d 2>/dev/null | jq .
Nếu thấy email, số điện thoại hay danh sách quyền dài dằng dặc, mọi request của bạn đang chở chúng đi — và bất kỳ ai chặn được request đều đọc được, vì phần thân JWT không hề được mã hoá.