Ký request
Cách Cake Mobile SDK ký các request của nó đến backend eKYC và FaceAuthen: HMAC-SHA256 trên một timestamp và request body, dùng JWT token riêng cho từng giao dịch làm secret key. Đây là một scheme khác với header ATV <ACCESS_ID>:<SIGNATURE> của backend. Trên các tenant FaceAuthen đã bật kiểm tra signature của thiết bị, SDK còn ký thêm bằng một khóa thiết bị đã đăng ký — xem Signature của thiết bị.
Mục đích
| Thông tin | Mô tả |
|---|---|
| Xác thực | Xác minh request thực sự đến từ một client được ủy quyền. |
| Toàn vẹn | Đảm bảo request body không bị can thiệp trong quá trình truyền. |
| Chống replay | Timestamp giới hạn thời gian một request bị chặn lại còn có thể dùng được. |
Input để ký
| Tham số | Kiểu | Bắt buộc | Mặc định | Mô tả |
|---|---|---|---|---|
timestamp | string | Có | — | Timestamp theo RFC3339, ví dụ 2019-04-21T18:00:15+07:00. Được gửi trong header X-TV-Timestamp. |
requestBody | string | Có | — | Request body thô. Hoạt động với cả JSON và multipart form data. |
jwtToken | string | Có | — | Token từ Tạo giao dịch, dùng làm HMAC secret key. |
WARNING
JWT chính là khóa ký, nên nó là một credential, không chỉ là một định danh. Không bao giờ ghi log, không bao giờ lưu giữ nó quá phạm vi giao dịch, và không bao giờ gửi nó cho bên thứ ba.
Thuật toán
StringToSign = timestamp + "\n" + hex_encode(SHA256(requestBody))
Signature = Base64(HmacSha256(JWTToken, Utf8EncodingOf(StringToSign)))- Băm (hash) request body thô bằng SHA256 và mã hóa hex.
- Nối timestamp và hash đó bằng một dấu xuống dòng.
- Mã hóa UTF-8 kết quả.
- Áp dụng HMAC-SHA256 dùng JWT token làm khóa.
- Mã hóa Base64 digest thu được.
JavaScript
function signRequest(requestBody, jwtToken) {
// Generate RFC3339 timestamp
const date = new Date();
const timestamp = date.toISOString();
// Generate hashed body
const hashedBody = CryptoJS.SHA256(requestBody).toString(CryptoJS.enc.Hex);
// Create string to sign
const stringToSign = `${timestamp}\n${hashedBody}`;
// Generate HMAC-SHA256 signature
const hmac = CryptoJS.HmacSHA256(stringToSign, jwtToken);
// Convert to hex and then Base64 encode
const hexHash = hmac.toString(CryptoJS.enc.Hex);
const signature = CryptoJS.enc.Base64.stringify(CryptoJS.enc.Hex.parse(hexHash));
return {
timestamp: timestamp,
signature: signature
};
}Go
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"time"
)
func signRequest(requestBody, jwtToken string) (string, string, error) {
// Generate RFC3339 timestamp
timestamp := time.Now().Format(time.RFC3339)
// Generate hashed body
hashFunc := sha256.New()
if _, err := hashFunc.Write([]byte(requestBody)); err != nil {
return "", "", err
}
hashedBody := hex.EncodeToString(hashFunc.Sum(nil))
// Create string to sign
stringToSign := timestamp + "\n" + hashedBody
// Generate HMAC-SHA256 signature
h := hmac.New(sha256.New, []byte(jwtToken))
h.Write([]byte(stringToSign))
signature := base64.StdEncoding.EncodeToString(h.Sum(nil))
return timestamp, signature, nil
}Header của request
| Header | Bắt buộc | Mô tả |
|---|---|---|
X-TV-Timestamp | Có | Timestamp RFC3339 dùng trong StringToSign. |
X-Signature | Có | Signature đã mã hóa Base64. |
Authorization | Có | Bearer {jwt_token}. |
POST /api/face-auth HTTP/1.1
Host: <HOSTNAME>
Authorization: Bearer <JWT_TOKEN>
Content-Type: application/json
X-TV-Timestamp: <TIMESTAMP>
X-Signature: <SIGNATURE>
{
"cus_user_id": "user_00000001",
"ref_id": "ref_00000001"
}Xác minh phía server
Backend xác minh mỗi request bằng cách:
- Trích xuất
X-TV-TimestampvàX-Signaturetừ request. - Tạo lại signature dự kiến bằng cùng thuật toán.
- So sánh bằng một phép so sánh constant-time, để tránh làm lộ thông tin qua thời gian xử lý.
- Chỉ tiếp tục nếu các signature khớp nhau.
Signature của thiết bị
Trên tenant đã bật kiểm tra signature của thiết bị (device signature), Mobile SDK còn ký mỗi request mà nó gửi bằng token giao dịch, dùng khóa thiết bị đã đăng ký qua Khởi tạo khóa ký và Xác nhận khóa ký. SDK thêm ba header:
| Header | Bắt buộc | Mô tả |
|---|---|---|
Device-Id | Đi kèm các header signature | device_id mà khóa ký (signing credential) đã được đăng ký cho. Phải khớp chính xác với device_id đã lưu của khóa ký (phân biệt hoa thường). |
X-Device-Key-Id | Xem bên dưới | ID của khóa ký — key_id từ Init và Confirm. Khóa ký phải ở trạng thái ACTIVE. |
X-Device-Signature | Xem bên dưới | Chuỗi JWS dạng compact (typ: cake-request-proof+jwt, alg: ES256) do SDK tạo trên audience của môi trường, method, URL path đúng như tài liệu (ví dụ /v1/face_auth, không có tiền tố gateway), Content-Type, hash của body, hash của token, thời điểm phát hành và một jti duy nhất. |
POST /v1/face_auth HTTP/1.1
Authorization: Bearer <JWT_TOKEN>
Content-Type: application/json
Device-Id: <DEVICE_ID>
X-Device-Key-Id: <DEVICE_KEY_ID>
X-Device-Signature: <DEVICE_SIGNATURE>Các header này là bắt buộc khi Device-Id được gửi có một khóa ký ACTIVE, hoặc khi phiên bản SDK bằng hoặc cao hơn phiên bản bắt đầu áp dụng việc ký. Chúng áp dụng cho mọi request của SDK dùng token giao dịch: Xác minh Face Authen (POST /v1/face_auth), Tải ảnh lên (POST /v1/images, ký trên body multipart thô) và POST /v1/get_config.
- Request bị từ chối với
access_denied_exception(401100) khi signature bị thiếu, không hợp lệ, quá hạn hoặc bị dùng lại (replay), hoặc khi khóa ký màX-Device-Key-Idchỉ tới không ở trạng thái ACTIVE hay được đăng ký cho mộtDevice-Idkhác. - Khi phiên bản SDK tối thiểu đã được thiết lập, request từ một SDK cũ hơn, hoặc không có header
Sdk-Version, bị từ chối vớisdk_version_unsupported(2009005). - Body vượt giới hạn kích thước bị từ chối với
invalid_parameter_exception(4001000) khi signature là bắt buộc.
WARNING
Signature của thiết bị bảo vệ request trên đường truyền; nó không làm cho kết quả mà ứng dụng hiển thị trở nên đáng tin cậy. Hãy xác nhận mọi kết quả xác thực từ backend của bạn bằng Xác minh khóa xác thực giao dịch.
Bước tiếp theo
- Xác minh Face Authen — lệnh gọi chính của SDK mang theo signature này
- Tạo giao dịch — nơi khóa ký JWT đến từ
- Khởi tạo khóa ký — đăng ký khóa thiết bị mà SDK dùng để ký