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%
HS256 không tốn gì đo được — 99 657 so với 99 113 là nhiễu. RS256 lấy mất 72% thông lượng và làm độ trễ trung vị tăng gấp 3,7 lần. Cùng một tiêu chuẩn, cùng một thư viện, cùng một dòng mã; khác nhau ở phép toán bên dưới.

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á.