Mục lục thư viện
Thư viện tài liệuBản quyền
License offline
Token offline có chữ ký giúp phần mềm của bạn tiếp tục chạy khi máy mất mạng, mặc định 7 ngày, hoặc một khoảng thời gian do bạn quyết định trên Premium, mà vẫn không thể sao chép sang máy khác hay kéo dài bằng cách chỉnh đồng hồ.
8 phút đọc
Token offline là gì
Mỗi lần phần mềm đăng nhập thành công hoặc gửi heartbeat, server trả kèm trường offlineToken, trên mọi gói. Đây là một chuỗi gọn dạng clv1.<payload>.<signature>: payload là JSON mã hoá base64url, signature là chữ ký Ed25519 trên chuỗi clv1.<payload>, ký bằng khoá ký của ứng dụng. SDK kiểm tra token ngay tại máy bằng khoá công khai đã nhúng trong bản build, không cần gọi server.
Token offline chỉ là giấy phép tạm cho lúc mất mạng, không thay thế đăng nhập online. Khi có mạng, phần mềm vẫn đăng nhập và heartbeat như bình thường, và mỗi lần như vậy nhận về một token mới. Mọi gói đều có, kể cả Basic, ở cả hai chế độ bảo vệ. Phương pháp Offline license có chữ ký thêm quyền chọn thời hạn: bạn đặt thời hạn trong gói license, và việc này cần Premium.
Khi nào server cấp token
Server điền offlineToken trên mọi gói, kể cả Basic. Trường này chỉ là null khi Chế độ chỉ online đang bật, công tắc sinh ra để lệnh thu hồi luôn có hiệu lực. Công tắc này nằm ở Bảo mật ứng dụng › Bảo vệ, tab Phương pháp, nhóm Thực thi license, và chỉ chạy ở Bảo vệ Advanced.
Token dùng được bao lâu:
- Mặc định 7 ngày.
- Trên workspace có tính năng license offline, trường
offlineTtlSecondscủa gói quyết định thời hạn. Mặc định đây là tính năng của Premium; bạn kiểm tra ở Tài khoản & tổ chức › Gói & sử dụng, dòng License offline phải là Có.
Vì vậy Premium không có nghĩa là "được dùng offline", mà là "bạn chọn bao lâu".
Token được cấp lại sau mỗi lần activate, trial, register, login, redeem thành công và sau mỗi heartbeat. Vì vậy cửa sổ offline luôn tính từ lần cuối máy liên lạc được với server, không phải từ lần kích hoạt đầu tiên.
Đặt thời hạn offline cho gói license
Thời hạn offline là trường offlineTtlSeconds của gói license, tính bằng giây, từ 3600 (1 giờ) tới 7776000 (90 ngày); null là giữ 7 ngày. Form gói ở Phát hành & bản quyền › Đăng nhập & gói license (nút Thêm gói, hoặc bấm vào một gói để sửa) hiện chưa có ô cho trường này, nên bạn đặt nó qua API của dashboard bằng phiên đăng nhập có vai trò Quản trị hoặc Chủ sở hữu:
- Tạo gói mới:
POST /v1/apps/<appId>/policies, gửiofflineTtlSecondscùng các trường khác của gói. - Sửa gói có sẵn:
PATCH /v1/apps/<appId>/policies/<policyId>với body như dưới, ví dụ 3 ngày:
{ "offlineTtlSeconds": 259200 }
Bấm Lưu gói trong form sau này không làm mất giá trị đó, vì form không gửi trường này. Trên workspace chưa có license offline, tạo gói kèm thời hạn offline bị từ chối với mã plan_limit, và kể cả khi gói đã có giá trị, token vẫn chỉ 7 ngày cho tới khi workspace có tính năng.
Nội dung token
| Trường | Ý nghĩa |
|---|---|
app | Id ứng dụng (app_…) |
lic | Id license (lic_…) |
dev | SHA-256 (hex) của khoá công khai thiết bị: token chỉ hợp lệ trên đúng máy đó |
lexp | Hạn license (Unix giây), null nếu vĩnh viễn |
iat | Thời điểm cấp |
exp | Hạn token: iat cộng thời hạn offline, không bao giờ vượt lexp |
nbf | Từ CLV1.1: không hợp lệ trước thời điểm này (bằng iat) |
kid | Từ CLV1.1: id khoá đã ký, 16 ký tự hex đầu của SHA-256 khoá công khai ứng dụng |
wm | Từ CLV1.1: watermark người mua, chỉ có khi Forensic Watermarking bật |
wm có dạng <10 hex>.<6 hex>: nửa đầu chỉ ra license, nửa sau chỉ ra máy, trùng với grant.license.mark bạn nhận khi online. Khi Forensic Watermarking bật, bạn có thể hiển thị dấu này trong hộp About hay header log cả khi offline. Ngày ân hạn của gói (Ân hạn sau hạn (ngày)) chỉ áp dụng khi online: token offline không bao giờ sống quá hạn license.
Kiểm tra token bằng SDK
verifyOffline(token?) trả về payload khi token hợp lệ, ngược lại trả null. Không truyền tham số thì SDK dùng token mới nhất nó giữ trong bộ nhớ. Để dùng được qua nhiều lần khởi động, bạn cần:
- Lưu khoá thiết bị (
deviceKey) cố định, ví dụ trong keychain hoặc DPAPI. Khoá mới mỗi lần chạy thìdevkhông khớp và mọi token cũ bị từ chối. - Lưu
offlineTokensau mỗi lần đăng nhập và mỗi heartbeat, nếu nó khácnull. - Chỉ chuyển sang
verifyOffline()khi lỗi là lỗi mạng, không phải khi server đã trả lời từ chối.
import { ClavisClient, ClavisError, hashComponent } from "@clavisauh/sdk";
const clavis = new ClavisClient({
baseUrl: "https://api.example.com",
appId: "app_…",
appPublicKey: "…",
device: { fingerprint: hashComponent(machineId) },
deviceKey: await loadDeviceKey(), // cùng một khoá ở mọi lần chạy
security: { maxOfflineSec: 3 * 86400 }, // tuỳ chọn: không tin token cấp quá 3 ngày
});
async function unlock(licenseKey: string) {
try {
await clavis.init();
const grant = await clavis.activate(licenseKey);
if (grant.offlineToken) await saveToken(grant.offlineToken);
return { online: true, licenseId: grant.license.id };
} catch (err) {
// status 0 nghĩa là không nhận được câu trả lời: network_error hoặc timeout
if (err instanceof ClavisError && err.status !== 0) {
// Server đã trả lời, hoặc câu trả lời không xác minh được: không chuyển sang offline
if (clavis.revoked) await deleteToken();
throw err;
}
// Lỗi mạng: dùng token đã lưu
const payload = clavis.verifyOffline(await loadToken());
if (!payload) throw new Error("Cần kết nối mạng để xác thực license");
return { online: false, licenseId: payload.lic, until: payload.exp };
}
}
setInterval(() => {
clavis
.heartbeat()
.then((hb) => (hb.offlineToken ? saveToken(hb.offlineToken) : undefined))
.catch(() => undefined); // mất mạng: giữ token cũ
}, 30 * 60_000);
loadDeviceKey, saveToken, loadToken và deleteToken là hàm của bạn. verifyOffline() trả null khi:
- Không có token, hoặc license đã được báo thu hồi trong tiến trình này.
- Chữ ký không đến từ khoá nào trong
appPublicKey/appPublicKeyscủa bản build. appkhác ứng dụng này, hoặcdevkhác khoá thiết bị của máy này.exphoặclexpđã qua.iathoặcnbfnằm ở tương lai quá 60 giây, hoặc đồng hồ máy lùi quá 60 giây so với giờ server đã thấy.maxOfflineSeclà0, hoặc token đã cấp lâu hơnmaxOfflineSecgiây.
Chống lùi đồng hồ
Kẻ dùng lậu thường lùi đồng hồ để kéo dài token. SDK chặn theo hai cách. Thứ nhất, token có iat hoặc nbf ở tương lai quá 60 giây bị từ chối: máy đang chạy ở thời điểm trước cả lúc token được cấp, nghĩa là đồng hồ đã bị lùi. Thứ hai, trong cùng một tiến trình, SDK nhớ giờ server mới nhất trong các câu trả lời đã xác minh; đồng hồ chạy sau mốc đó quá 60 giây thì token bị từ chối. Biên 60 giây chừa chỗ cho lệch giờ nhỏ.
Khi phát hiện lùi giờ, bạn có thể báo về server lúc có mạng trở lại và đã đăng nhập lại. Báo cáo này cần phiên hợp lệ, tạo tín hiệu Lùi giờ khi offline trong Bảo mật ứng dụng › Bảo vệ; nó chỉ nâng điểm rủi ro, tự nó không chặn ai, và mỗi phiên chỉ được tính tối đa 5 báo cáo mỗi giờ.
await clavis.request("/v1/client/report", { kind: "offline_rewind" });
Giới hạn maxOfflineSec
maxOfflineSec là giới hạn bổ sung trên tuổi của token, tính từ iat:
null: không giới hạn thêm, chỉ theoexpcủa token.0: không bao giờ tin token offline.- Một số giây N: từ chối token đã cấp lâu hơn N giây.
Giá trị đến từ hai nguồn. Bản build tự đặt qua security.maxOfflineSec khi tạo ClavisClient. Server gửi gợi ý trong câu trả lời có ký của init() (securityHints.maxOfflineSec): 0 khi Chế độ chỉ online bật, null khi tắt. SDK giữ giá trị chặt hơn, nên server, kể cả server giả, không thể nới lỏng cấu hình trong bản build. Giá trị đang áp dụng đọc ở clavis.security.maxOfflineSec. Gợi ý từ server chỉ có hiệu lực sau khi init() thành công trong tiến trình đó.
Thu hồi và token offline
Token offline tự chứa đủ thông tin nên server không thể gọi nó về. Khi bạn thu hồi hoặc tạm khoá một license, mọi phiên của license kết thúc ngay và heartbeat kế tiếp nhận session_expired. Khi phần mềm đăng nhập lại trên máy đó, server trả license_revoked hoặc license_suspended; SDK đặt clavis.revoked thành true, bỏ token trong bộ nhớ, và verifyOffline() trả null cho tới hết tiến trình.
Cờ này chỉ nằm trong bộ nhớ, nên hãy xoá token đã lưu khi thấy nó (như ví dụ trên), nếu không token cũ vẫn dùng được sau khi khởi động lại. Một máy không bao giờ có mạng thì chạy tiếp tới exp: thời hạn offline, mặc định 7 ngày, chính là độ trễ tối đa để một lần thu hồi tới được máy mất mạng.
Xoay khoá ký
Token mang kid, SDK chọn đúng khoá trong appPublicKeys để kiểm tra; token cũ không có kid (cấp trước CLV1.1) được thử với mọi khoá tin cậy. Bản build chỉ chứa khoá mới sẽ từ chối token ký bằng khoá cũ, và người dùng cần lên mạng một lần để nhận token mới. Vì vậy hãy phát hành bản build mang cả hai khoá trước khi kích hoạt khoá mới.
Lưu ý thêm
- Token chép sang máy khác bị từ chối vì
devkhông khớp. - Cài lại hệ điều hành mà mất khoá thiết bị thì token cũ vô dụng, máy phải lên mạng để đăng nhập lại.
- License vĩnh viễn có
lexplànull, nênexpchỉ bằngiatcộng thời hạn offline. - Chỉnh đồng hồ tới trước chỉ làm token hết hạn sớm hơn, không có lợi cho người dùng lậu.
- Chi tiết định dạng token có trong đặc tả giao thức.