auth_api_key của OCA ra đời trước API key gốc của Odoo hai năm. Dòng thời gian từ 2018 tới nay, bản lõi chiếm lấy những gì ở Odoo 18 và 19, và phần nào vẫn chỉ OCA có.
Đây là bài đi kèm với Bootstrapping a /json/2 API Key for Odoo Mobile Apps. Bài đó nói về việc lấy một API key gốc của Odoo khi trong tay bạn chỉ có tên đăng nhập và mật khẩu. Bài này lùi lại một bước và đặt một câu hỏi xuất hiện ở gần như mọi dự án tích hợp: Odoo đã có API key theo từng người dùng từ 14.0, vậy vì sao OCA vẫn duy trì auth_api_key — cả một hệ thống key riêng — và vì sao các bộ REST của OCA lại phụ thuộc vào nó thay vì dùng cái gốc?
Bạn có thể trả lời bằng một bảng so sánh tính năng, nhưng câu trả lời thành thật là câu trả lời lịch sử. auth_api_key không được dựng lên như một phương án thay thế cho key gốc — nó ra đời trước hai năm. Bản lõi bắt kịp, rồi đi tiếp, và quan hệ giữa hai bên đổi theo. Nên thay vì một bảng so sánh tĩnh, đây là dòng thời gian, mỗi bước đều neo vào đoạn mã đã thực sự được phát hành.
Dòng thời gian nhìn nhanh
flowchart TB
A["2018 · Odoo 10<br/>OCA phát hành<br/>auth_api_key"]
B["2020 · Odoo 14<br/>Bản lõi có<br/>res.users.apikeys"]
C["2021 · Odoo 14<br/>OCA thêm<br/>group + server_env"]
D["2024 · Odoo 17<br/>OCA thêm<br/>fastapi_auth_api_key"]
E["2024 · Odoo 18<br/>Bản lõi thêm<br/>auth=bearer"]
F["2025 · Odoo 19<br/>Bản lõi phát hành<br/>/json/2"]
G["2025-26 · Odoo 19<br/>OCA sinh ra<br/>key gốc"]
A -->|"bản lõi chưa có key"| B
B -->|"route tùy chỉnh vẫn bỏ ngỏ"| C
C -->|"REST cần một điểm móc"| D
D -->|"bản lõi chiếm lấy ranh giới"| E
E -->|"key thành cửa chính"| F
F -->|"vẫn thiếu bước khởi tạo"| GVẫn câu chuyện đó, với các khoảng trống nói rõ ra:
| Năm | Odoo | Bản lõi phát hành gì | Khoảng trống | Câu trả lời của OCA |
|---|---|---|---|---|
| 2018 | 10 → | XML-RPC / JSON-RPC, chỉ có người dùng + mật khẩu | Không có xác thực bằng key; controller tùy chỉnh phải tự nghĩ ra cách riêng | auth_api_key (mầm mống từ Akretion 2017 → ACSONE, nhánh 10.0) |
| 2020 | 14 | res.users.apikeys gốc — băm, theo từng người dùng, cho RPC | Không phủ các controller tùy chỉnh | Vẫn phải cần module của OCA |
| 2021 | 14 | — | Không có phân phạm vi, không đưa được secret ra khỏi database | auth_api_key_group, auth_api_key_server_env, base_rest_auth_api_key |
| 2024 (đầu năm) | 17 | — | Không có điểm móc chính thức cho REST/FastAPI | fastapi_auth_api_key |
| 2024 (tháng 9) | 18 | Phương thức auth="bearer" dùng chung — key gốc nay canh được cả route tùy chỉnh | Ranh giới controller tùy chỉnh bị bào mòn | — |
| 2025 | 19 | /json/2 — key bearer thành cách xác thực đối ngoại chính | Cấp phát danh tính cho dịch vụ, chặn theo nhóm, secret theo môi trường, khởi tạo key đầu tiên | — |
| 2025–26 | 19 | — | Khoảng trống khởi tạo (xem bài trước) | auth_api_key_native_generate — bổ trợ cho bản lõi, không thay thế |
(Cột Odoo là nhánh mà mỗi hạng mục xuất hiện lần đầu — đã tự kiểm chứng trên lịch sử git — chứ không phải phiên bản Odoo mới nhất của năm đó. Ngày kiểm chứng và commit được dẫn ngay trong bài.)
Hình dạng câu chuyện: OCA tới trước, bản lõi bắt kịp ở trường hợp phổ thông rồi tới Odoo 18 thì chiếm luôn cả ranh giới controller tùy chỉnh, và OCA chuyển từ vai thay thế sang vai một hệ sinh thái xếp lớp quanh bản lõi. Ta đi qua từng chặng.
2018: trước khi Odoo có bất kỳ API key nào
Hồi đó, API đối ngoại duy nhất là XML-RPC và JSON-RPC, và cả hai đều xác thực bằng tên đăng nhập với mật khẩu ở mọi lời gọi. Trong bản lõi không có khái niệm API key ở bất kỳ đâu. Nếu bạn phơi ra một controller HTTP tùy chỉnh — chẳng hạn một endpoint REST cho một đối tác tích hợp — bạn phải tự viết lấy phần xác thực.
Đó là khoảng trống mà auth_api_key sinh ra để lấp. Phần mã cho phương thức xác thực này truy về công việc của Sébastien Beau tại Akretion năm 2017; ACSONE đóng gói nó thành module auth_api_key, với commit đầu tiên (6645f37e1, “New generic module du define REST services”) đáp xuống nhánh 10.0 ngày 31/1/2018 — thông điệp commit nêu đích danh REST service là lý do nó tồn tại:
# server-auth/auth_api_key/models/ir_http.py
# Copyright 2018 ACSONE SA/NV
# Copyright 2017 Akretion — @author Sébastien BEAU
class IrHttp(models.AbstractModel):
_inherit = "ir.http"
@classmethod
def _auth_method_api_key(cls):
headers = request.httprequest.environ
api_key = headers.get("HTTP_API_KEY")
if api_key:
request.update_env(user=1)
auth_api_key = request.env["auth.api.key"]._retrieve_api_key(api_key)
if auth_api_key:
request._env = None
request.update_env(user=auth_api_key.user_id.id)
request.auth_api_key = api_key
request.auth_api_key_id = auth_api_key.id
return True
_logger.error("Wrong HTTP_API_KEY, access denied")
raise Unauthorized()
Định nghĩa _auth_method_api_key là đăng ký thêm một giá trị cho tham số auth= của @http.route. Toàn bộ mẹo nằm ở đó, và tới hôm nay nó vẫn là lý do tồn tại của module:
@http.route("/my/custom/endpoint", auth="api_key", type="json")
def my_endpoint(self):
# request now runs as the service user mapped to the presented key
...
Mô hình dữ liệu đứng sau nó vốn đã — và vẫn — đơn giản một cách có chủ đích. Một key không phải thông tin đăng nhập cá nhân của một người dùng; nó là một bản ghi có tên, ánh xạ một chuỗi không nói lên gì tới một người dùng dịch vụ:
# server-auth/auth_api_key/models/auth_api_key.py
class AuthApiKey(models.Model):
_name = "auth.api.key"
name = fields.Char(required=True)
key = fields.Char(required=True, help="The API key. Enter a dummy value ... "
"if it is obtained from the server environment configuration.")
user_id = fields.Many2one("res.users", string="User", required=True,
help="The user used to process the requests "
"authenticated by the api key")
Ba trường thiết yếu (còn có một trường active được tính toán): một cái tên, chuỗi key, và người dùng mà request sẽ chạy dưới danh nghĩa. Thứ này2 trở thành viên gạch xác thực cho framework REST của OCA (base_rest, và sau này là fastapi). Hãy nhớ hình dạng này — toàn bộ phần còn lại của câu chuyện là việc bản lõi từ từ dựng hệ thống key của riêng mình ngay bên cạnh, mà không bao giờ tiếp nhận cái này.
2020 (Odoo 14): bản lõi cuối cùng cũng có API key — nhưng hẹp
Odoo 14 đưa vào res.users.apikeys gốc (đã kiểm chứng: model này không có trên nhánh 13.0 và có trên 14.0 — GitHub, kiểm tra ngày 17/7/2026)9. Đây là hệ thống mà bài trước đã mổ xẻ: một key là thông tin đăng nhập của chính một người dùng thật, lưu dưới dạng băm, mang theo một scope, tự người dùng tạo ra từ Preferences → Account Security. Một trường hợp dùng có ghi trong tài liệu là xác thực hai lớp: điểm móc _rpc_api_keys_only của bản lõi tồn tại “để được ghi đè khi cần giới hạn truy cập RPC chỉ qua API key, ví dụ cho 2FA” (theo chính docstring của nó) — một người dùng bật 2FA không thể gửi mật khẩu trần qua RPC, nên họ trình ra một key thay thế.
Điểm mấu chốt: key gốc xác thực cho API đối ngoại của chính Odoo — trước là RPC, nay là /json/2. Không phải cho phần đăng nhập web tương tác: trong _check_credentials, nhánh API key chỉ được chạm tới ở đường không tương tác (if not interactive:), nên bạn không đăng nhập web client bằng key được. Và ở Odoo 14 thì vẫn chưa có điểm móc nào cho một controller do bạn viết. Nên ngay khi key gốc xuất hiện, chúng không làm auth_api_key trở nên thừa — hai bên chồng lấn ở phần “xác thực một lời gọi RPC”, nhưng trường hợp dùng ban đầu (canh endpoint của tôi) vẫn nằm y nguyên chỗ cũ.
Đó là điểm cốt lõi của phép so sánh từ Odoo 14 đến 17 — một ranh giới thiết kế, không phải một sự bỏ sót. (Hãy ghi nhớ điều này: Odoo 18 dời ranh giới đó, và ta sẽ tới đó ở dưới.)
res.users.apikeys gốc (2020) | auth.api.key của OCA (2018) | |
|---|---|---|
| Một key là… | thông tin đăng nhập thuộc về một người dùng | một bản ghi do quản trị viên ánh xạ tới bất kỳ người dùng nào |
| Cách lưu | băm (chỉ hiện một lần) | mặc định là văn bản thuần trong database |
| Xác thực cho (v14–17) | API đối ngoại của chính Odoo (RPC) — không phải đăng nhập web, không phải controller của bạn | bất kỳ @route(auth="api_key") nào bạn định nghĩa |
| Giá trị của key | luôn là một chuỗi ngẫu nhiên 160 bit do máy chủ sinh, chỉ hiện một lần | quản trị viên đặt được giá trị tùy ý (kể cả lấy từ cấu hình môi trường) |
| Cách cấp phát | người dùng tự tạo trên giao diện, hoặc gọi generate() bằng code — mà lời gọi này lại cần đã có sẵn một key gốc (khoảng trống khởi tạo) | quản trị viên tạo bản ghi; không cần key nào có trước |
| Hết hạn / xoay vòng | có sẵn trường hết hạn; người dùng thường bị chặn ở 3 tháng; có generate()/thu hồi | model nền không có trường hết hạn; việc xoay vòng là quy trình của riêng bạn |
| Chạy dưới danh nghĩa | người dùng sở hữu key (áp ACL và record rule) | bất kỳ user_id nào bạn gán (áp ACL và record rule) |
Hãy để ý khác biệt về danh tính ở đây thực chất là gì. Cả hai loại key rốt cuộc đều quy về một res.users — auth.api.key của OCA bắt buộc có user_id, còn một key gốc có thể thuộc về một người dùng “bot” không đăng nhập được (đúng khuôn mẫu mà bài trước trích từ tài liệu Odoo), và cả hai đều chạy dưới ACL và record rule của người dùng đó. Vậy đây không phải chuyện “người thật so với tài khoản dịch vụ”. Còn lại hai khác biệt thật: (1) cấp phát — OCA cho phép quản trị viên gán một giá trị key tùy ý cho bất kỳ người dùng nào mà không cần key có trước, trong khi bản lõi luôn sinh ra giá trị ngẫu nhiên và lời gọi generate() bằng code lại đòi phải có sẵn một key gốc (để ngỏ bài toán khởi tạo key đầu tiên — chủ đề của bài trước); (2) vòng đời — key gốc có sẵn cơ chế hết hạn và thu hồi; model nền của OCA thì không có cả hai.
Ranh giới từng rõ ràng ở v14–17: key gốc xác thực cho API của Odoo; key OCA xác thực cho API của bạn. Hồi đó đúng. Odoo 18 là lúc điều đó thôi còn đúng — và đó mới là phần thú vị của câu chuyện.
2021 (Odoo 14): OCA thôi lấp khoảng trống và bắt đầu dựng khuôn mẫu
Tới 2021, các module của OCA không còn chỉ là “thứ bản lõi đang thiếu”. Camptocamp bổ sung những năng lực mà tới nay bản lõi vẫn chưa có.
auth_api_key_group3 — phân phạm vi có thể cắm thêm. Một key gốc mang đúng một chuỗi scope, và /json/2 chấp nhận key không scope hoặc key scope rpc (câu SQL khớp là scope IS NULL OR scope = 'rpc'); không có cách nào nói “key này được chạm endpoint A nhưng không được chạm B”. OCA đưa phần phân phạm vi ra khỏi key, thành một khái niệm mở rộng được:
# server-auth/auth_api_key_group/models/auth_api_key_group.py
class AuthApiKeyGroup(models.Model):
_name = "auth.api.key.group"
name = fields.Char(required=True)
code = fields.Char(required=True)
auth_api_key_ids = fields.Many2many("auth.api.key", ...)
Phần manifest thành thật một cách dễ chịu về việc bản thân nó làm được gì:
Bản thân việc nhóm lại không làm gì cả. Tính năng này được dự tính để các module khác dùng, nhằm giới hạn quyền truy cập vào dịch vụ hoặc bản ghi dựa trên các nhóm key.
Đó là một điểm móc, không phải một tính năng — bên tiêu thụ mới quyết định một nhóm cho phép làm gì.
auth_api_key_server_env4 — đưa key ra khỏi database. Module này trả lời một nỗi đau vận hành có thật: mặc định, key của OCA nằm ở cột key dưới dạng văn bản thuần. server_env chuyển giá trị đó sang cấu hình môi trường của máy chủ:
# server-auth/auth_api_key_server_env/models/auth_api_key.py
class AuthApiKey(models.Model):
_inherit = ["auth.api.key", "server.env.techname.mixin", "server.env.mixin"]
def _server_env_section_name(self):
# section in the config file: [api_key_<name>]
return f"api_key_{getattr(self, self._server_env_section_name_field)}"
@property
def _server_env_fields(self):
base_fields = super()._server_env_fields
return {"key": {}, **base_fields}
Bạn đặt một giá trị giả vào database còn key thật sống trong một mục [api_key_<name>] của cấu hình môi trường — nên, theo đúng manifest, bạn “tránh trộn lẫn key giữa các môi trường khi khôi phục database.” Khôi phục production lên staging thì key ở staging vẫn là thứ mà cấu hình của staging nói. Đó là cách xử lý secret theo 12-factor; key gốc không có gì tương đương, vì phần băm của một key gốc luôn đi theo bản dump database.
(base_rest_auth_api_key cũng xuất hiện cùng năm, nối auth_api_key vào lược đồ bảo mật OpenAPI của base_rest — tức framework REST tiêu thụ viên gạch nền này.)
ℹ️ Tại thời điểm viết bài,
auth_api_key_server_envchưa được chuyển lên quá 18.0 — nhánhserver-auth19.05 chỉ cóauth_api_keyvàauth_api_key_group(auth_api_key_native_generatevẫn còn là một PR đang mở6). Đoạn mã bên dưới lấy từ nhánh 18.0. Nếu bạn cần nó trên 19, đó là một lần migration, không phải một lần cài đặt.
2024 (đầu năm, Odoo 17): xác thực bằng api key thành một dependency chính thức của FastAPI
Khi module fastapi của OCA biến FastAPI thành một cách dựng endpoint cho Odoo, module fastapi_auth_api_key (commit đầu caaa7fcb7, 28/2/2024, trên nhánh 17.0 — và tại thời điểm viết bài vẫn chưa chuyển lên 19.0) biến auth.api.key thành một dependency của FastAPI, rồi dùng khái niệm nhóm từ 2021 để chặn từng endpoint:
# rest-framework/fastapi_auth_api_key/dependencies.py
def authenticated_auth_api_key(
key: Annotated[str, Depends(APIKeyHeader(name=HTTP_API_KEY_HEADER))],
env: Annotated[Environment, Depends(odoo_env)],
endpoint: Annotated[FastapiEndpoint, Depends(fastapi_endpoint)],
) -> AuthApiKey:
...
admin_env = Environment(env.cr, SUPERUSER_ID, {})
auth_api_key = admin_env["auth.api.key"]._retrieve_api_key(key) # 401 if unknown
# Ensure the key is authorized for THIS endpoint via its group:
if (endpoint.sudo().auth_api_key_group_id
and auth_api_key not in endpoint.sudo().auth_api_key_group_id.auth_api_key_ids):
raise HTTPException(status_code=401, detail=env._("Unauthorized"))
return auth_api_key
Một endpoint khai báo nó chấp nhận nhóm key nào; dependency lo phần thực thi. Đây là phần thưởng của việc “dùng chung một viên gạch nền”: một bản ghi auth.api.key vừa chạy được như một thông tin đăng nhập trong header, vừa như một dependency chính thức của FastAPI, có chặn theo nhóm cho từng endpoint, mà không cần tới một hệ thống key thứ hai. Một key bearer gốc vẫn xác thực được một request như vậy, và key gốc cũng có trường scope — nhưng tầng route auth="bearer" có sẵn chỉ kiểm tra scope='rpc' và không cho cấu hình gì theo từng endpoint, nên việc chặn kiểu “key này được chạm A nhưng không được chạm B” không có thứ tương đương cấu hình được ở đó.
2024 (tháng 9, Odoo 18): bản lõi tự dời ranh giới
Đây là chỗ câu chuyện rẽ hướng — và điều quan trọng là nó rẽ trước /json/2 một năm, chỗ này rất dễ nhầm. Ở Odoo 18, bản lõi thêm một phương thức auth="bearer" dùng chung vào ir.http (commit e6d5945110, "[IMP] core: bearer authorization header", 16/9/2024; có trên nhánh 18.011 và 19.012, không có trên 17.0 trở về trước — GitHub, kiểm tra ngày 17/7/2026):
# odoo/addons/base/models/ir_http.py (core, 18.0+)
@classmethod
def _auth_method_bearer(cls):
...
if token := get_http_authorization_bearer_token():
# 'rpc' scope does not really exist, we basically require a global key (scope NULL)
uid = request.env['res.users.apikeys']._check_credentials(scope='rpc', key=token)
if not uid:
raise Unauthorized("Invalid apikey", www_authenticate=WWWAuthenticate('bearer'))
...
Đó chính là cơ chế mở rộng mà auth_api_key đã khai phá năm 2018 — một giá trị cho tham số auth= của @http.route — chỉ khác là nay nó nằm trong bản lõi và dựa trên key gốc. Mọi module trên 18.0 trở đi đều viết được:
@http.route("/my/custom/endpoint", auth="bearer", type="http")
def my_endpoint(self):
# authenticated by a native res.users.apikeys bearer token, no OCA module
...
Vậy là cái ranh giới đứng vững từ Odoo 14 tới 17 — key gốc xác thực cho API của Odoo, key OCA xác thực cho API của bạn — bị bào mòn ở Odoo 18. Key gốc nay canh được cả một controller do bạn viết. Lý do lớn nhất khiến auth_api_key tồn tại nay đã có sẵn trong hộp.
Điều đó không làm bộ module của OCA thành vô dụng — nó chỉ thu hẹp phần OCA còn giữ riêng xuống bốn thứ mà auth="bearer" vẫn chưa cho bạn:
- Sự tiện lợi khi cấp phát — quản trị viên tạo được một key OCA cho bất kỳ người dùng nào và đặt giá trị của nó từ cấu hình môi trường, không cần key có trước. Lời gọi
generate()của bản lõi luôn sinh ra một giá trị ngẫu nhiên và đòi phải có sẵn một key gốc. - Chặn endpoint theo nhóm —
auth_api_key_groupcộng với các bên tiêu thụ FastAPI/base_rest cho phép một key được phép chạm endpoint A mà không được chạm B.auth="bearer"gốc là được ăn cả ngã về không ở tầng xác thực của route (key toàn cục hợp lệ nào cũng qua; muốn kiểm soát mịn hơn thì phải tự viết kiểm tra ACL/record rule bên trong handler). - Secret nằm ngoài database —
auth_api_key_server_envgiữ giá trị key ra khỏi bản dump. Phần băm của key gốc thì luôn đi theo bản dump. - Khởi tạo key đầu tiên — như bài trước đã chỉ ra, bản lõi vẫn chưa có đường đi tự động từ mật khẩu sang key đầu tiên.
(Chiều ngược lại cũng đúng: key gốc có sẵn cơ chế hết hạn và thu hồi mà model nền auth.api.key của OCA không có — một điểm nghiêng về phía bản lõi, nói kỹ ở phần đánh đổi bên dưới.)
2025 (Odoo 19): /json/2 biến key bearer thành cách xác thực đối ngoại chính
Odoo 19 phát hành /json/2 (controller addons/rpc/controllers/json2.py13 — mới có ở 19.0, không có ở 18.0) và đưa API key bearer thành cách xác thực chính cho tầng truyền đối ngoại, đồng thời ngừng hỗ trợ các API XML-RPC/JSON-RPC dựa trên mật khẩu để chuyển sang JSON-2 (chúng bị ngừng hỗ trợ chứ chưa bị gỡ — tài liệu Odoo xếp lịch gỡ ở Odoo 22; xem bài về chuyển đổi). Phần ống dẫn auth="bearer" vốn đã có từ 18; 19 là lúc key gốc trở thành đường mặc định, đường cửa chính để với tới API của chính Odoo.
Vậy tới 2025, OCA không còn là cách duy nhất, thậm chí không còn là một trong số ít cách, để bảo vệ endpoint của bạn bằng key. Nó là thứ bạn tìm tới khi cần một trong bốn điều nói trên.
Rồi tới dấu hiệu rõ nhất cho quan hệ đã đổi chiều: auth_api_key_native_generate (module trong bài trước). Nó nhắm vào chính repo server-auth (tại thời điểm viết bài vẫn đang được rà soát ở PR #9706), nhưng nó không thêm một hệ thống key nào nữa. Nó sinh ra res.users.apikeys gốc bằng code — nó khép lại đúng cái khoảng trống khởi tạo mà bản lõi để ngỏ, và trao cho bạn một key của bản lõi để dùng với /json/2.
Cả cung đường nằm gọn trong một module. Năm 2018, OCA tồn tại vì bản lõi không có gì. Tới 2026, OCA viết mã mà nhiệm vụ là tiếp nguyên liệu cho hệ thống key của chính bản lõi. Hai bên không còn là đối thủ; chúng ghép được vào nhau.
Một đánh đổi thành thật không được bỏ qua: cách lưu trữ
Neo vào mã nguồn nghĩa là phải nêu cả chỗ OCA yếu hơn, chứ không chỉ chỗ nó linh hoạt hơn.
res.users.apikeys gốc lưu một giá trị băm. Lấy trộm được database cũng không khôi phục được key. Còn auth.api.key của OCA, theo mặc định, lưu key dưới dạng văn bản thuần ở cột key, và so sánh bằng consteq an toàn với tấn công đo thời gian:
# server-auth/auth_api_key/models/auth_api_key.py
@tools.ormcache("key")
def _retrieve_api_key_id(self, key):
if not self.env.user.has_group("base.group_system"):
raise AccessError(...)
for api_key in self.search([], limit=None):
if api_key.key and consteq(key, api_key.key): # constant-time compare
return api_key.id
raise ValidationError(...)
consteq bảo vệ phép so sánh trước tấn công đo thời gian, nhưng giá trị khi nằm yên thì vẫn đọc được — ai có bản dump, một bản sao lưu, hay một câu SELECT đều thấy toàn bộ key đang hoạt động. Cách giảm nhẹ là auth_api_key_server_env: giữ giá trị key ra khỏi database. Nó rất nên dùng bất cứ khi nào có sẵn hệ thống quản lý secret khi vận hành — nhưng lưu ý hai điểm. Thứ nhất, nó không khiến một secret tự nhiên thành an toàn: key vẫn nằm ở dạng văn bản thuần ở đâu đó (file cấu hình, biến môi trường, hoặc một secret được mount vào), nên nó vẫn đòi cách xử lý thông thường — một kho secret thật (Vault, secret của Kubernetes/Docker), quyền file chặt chẽ, và việc xoay vòng. Thứ hai, như đã nói ở trên, auth_api_key_server_env chưa được chuyển lên 19.0. (Cũng nên biết rằng việc tra key quét toàn bộ bằng search([]) rồi consteq — ổn với một tập key tích hợp có giới hạn, không ổn với xác thực theo từng người dùng ở quy mô lớn. Điều đó càng củng cố đúng mục đích sử dụng của nó.)
Còn một khoảng trống vận hành thứ hai mà chỉ so sánh cách lưu trữ thì không thấy: vòng đời. res.users.apikeys gốc có sẵn expiration_date, có mức trần 3 tháng ghi trong tài liệu cho người dùng thường, và có sẵn cơ chế tạo/thu hồi. Model nền auth.api.key của OCA không có trường hết hạn nào cả — một key còn hiệu lực cho tới khi ai đó xóa bản ghi, và việc xoay vòng hoàn toàn là quy trình của bạn. Với bất cứ thứ gì sống lâu, đó là lý lẽ nghiêng về bản lõi còn mạnh hơn cả chuyện băm.
⚠️
auth.api.keymặc định của OCA giữ key ở dạng văn bản thuần trong cộtkeyvà không có cơ chế hết hạn sẵn có. Với một hệ thống nhạy cảm, hãy dùng kèmauth_api_key_server_env(dựa trên một kho secret đúng nghĩa) và tự thêm quy trình xoay vòng/hết hạn của riêng bạn.
Vậy dùng cái nào?
Chúng không được xếp hạng; chúng hợp với những việc khác nhau — và sau dòng thời gian ở trên, cách chia này hẳn thấy tự nhiên.
flowchart TD
A["Bạn đang bảo vệ cái gì?"] --> B["Chính các model của Odoo qua /json/2 hoặc RPC"]
A --> C["Một endpoint tùy chỉnh do bạn viết"]
B --> N1["res.users.apikeys gốc"]
C --> D{"Odoo 18 trở lên?"}
D -- "Không (14-17)" --> O1["auth_api_key của OCA"]
D -- "Có" --> E{"Cần chặn theo nhóm, cấp phát bởi<br/>quản trị viên/môi trường, hay secret ngoài database?"}
E -- "Không" --> N2["auth=bearer gốc"]
E -- "Có" --> O2["auth_api_key của OCA<br/>+ group / server_env"]Cũng quyết định đó dưới dạng bảng tra, kèm các trường hợp rìa:
| Tình huống của bạn | Hãy dùng |
|---|---|
Gọi vào chính các model của Odoo qua /json/2 (ứng dụng di động, client RPC, ETL) | res.users.apikeys gốc — băm, thuộc về người dùng, và tầng truyền vốn đã hiểu nó |
| Canh một endpoint tùy chỉnh trên Odoo 18 trở lên, chỉ cần kiểm tra được ăn cả ngã về không | auth="bearer" gốc — không cần module nào thêm |
| Canh một endpoint tùy chỉnh trên Odoo 17 trở xuống | auth_api_key của OCA — bản lõi chưa có auth="bearer" |
| Chặn theo từng endpoint / từng key (“key này được chạm A nhưng không được chạm B”) | OCA + auth_api_key_group (cộng các bên tiêu thụ FastAPI / base_rest) |
| Key do quản trị viên cấp phát, giá trị lấy từ cấu hình môi trường | auth_api_key của OCA (cộng auth_api_key_server_env) |
| Secret buộc phải nằm ngoài bản dump database | OCA + auth_api_key_server_env (dựa trên một kho secret thật) |
| Người dùng tự sở hữu, có sẵn cơ chế hết hạn và vòng đời thu hồi | Bản lõi |
Với Odoo 17 trở xuống, quy tắc cũ vẫn là cách định hướng nhanh nhất: key gốc xác thực cho API của Odoo, key OCA xác thực cho API của bạn. Từ Odoo 18, ranh giới đó không còn — auth="bearer" cho phép key gốc canh cả endpoint của bạn — nên câu hỏi chuyển từ “endpoint này của ai?” sang “tôi có cần chặn theo nhóm, cấp phát bởi quản trị viên/môi trường, hay secret nằm ngoài database không?” Nếu có, dùng OCA; nếu không, một mình bản lõi là đủ. Dù thế nào, nhờ auth_api_key_native_generate, bạn chạy được cả hai trên cùng một hệ thống.
🔒 Khuyến nghị về bảo mật. Hãy mặc định chọn key gốc — băm khi lưu, có sẵn cơ chế hết hạn và thu hồi. Chỉ dùng bộ module OCA khi vướng một trong bốn khoảng trống nói trên. Khi dùng,
auth_api_key_server_envlà rất nên có (đừng bao giờ để một key đang hoạt động nằm thuần trong database) — dựa trên một kho secret thật (Vault, secret của Kubernetes/Docker), quyền file chặt chẽ, và một quy trình xoay vòng/hết hạn rõ ràng, vì model nền không cho bạn thứ nào trong số đó. Với một dự án Odoo 18+/19 làm mới từ đầu, hãy bắt đầu từ bản lõi rồi chỉ thêm module OCA ở đúng chỗ có một khoảng trống cụ thể đòi hỏi.
Những điểm rút ra
- OCA tới trước.
auth_api_key(2018, gốc rễ từ Akretion 2017, nhánh 10.0) ra đời trướcres.users.apikeysgốc (2020) — nó chưa bao giờ là phương án thay thế cho bản lõi, nó lấp một khoảng trống mà bản lõi còn để ngỏ thêm hai năm nữa. - Bản lõi bắt kịp trong hai đợt. Odoo 14 chiếm phần RPC; Odoo 18 thêm một
auth="bearer"dùng chung (dựa trên key gốc) cho phép key gốc canh cả controller tùy chỉnh — đúng cái ranh giới từng biện minh cho sự tồn tại của OCA. Rồi/json/2của Odoo 19 biến key bearer thành cách xác thực đối ngoại chính. - Phần còn lại của OCA hẹp hơn nhưng có thật. Chặn theo nhóm cho từng endpoint, cấp phát dựa trên quản trị viên/môi trường, secret nằm ngoài bản dump, và bước khởi tạo từ mật khẩu sang key đầu tiên. Bản lõi vẫn chưa làm được thứ nào.
- Quan hệ đã đảo chiều. Với
auth_api_key_native_generate, OCA nay tiếp nguyên liệu cho hệ thống key của bản lõi thay vì thay thế nó. Từ đối thủ thành ghép được vào nhau. - Để ý cách lưu trữ và vòng đời. Key OCA mặc định nằm thuần trong database và không có hạn dùng; hãy dùng
auth_api_key_server_env(dựa trên một kho secret thật) cộng quy trình xoay vòng của riêng bạn. Key gốc thì được băm và có sẵn cơ chế hết hạn/thu hồi ngay từ đầu.
-
auth_api_key — commit đầu tiên 6645f37e, 'New generic module du define REST services' (10.0, 31/1/2018) — OCA server-auth ↩︎
-
mã nguồn module auth_api_key (19.0) — OCA server-auth ↩︎
-
mã nguồn module auth_api_key_group (19.0) — OCA server-auth ↩︎
-
mã nguồn module auth_api_key_server_env (18.0 — chưa chuyển lên 19.0) — OCA server-auth ↩︎
-
nội dung nhánh 19.0 — OCA server-auth ↩︎
-
PR #970 — [19.0][ADD] auth_api_key_native_generate (đang mở tại thời điểm viết bài) — OCA server-auth ↩︎
-
fastapi_auth_api_key — commit đầu tiên caaa7fcb (17.0, 28/2/2024) — OCA rest-framework ↩︎
-
mã nguồn module fastapi_auth_api_key (17.0) — OCA rest-framework
-
res.users.apikeys trên nhánh 14.0 — odoo/addons/base/models/res_users.py — Odoo ↩︎
-
commit e6d59451 — [IMP] core: bearer authorization header (16/9/2024) — Odoo ↩︎
-
controller /json/2 trên nhánh 19.0 — addons/rpc/controllers/json2.py — Odoo ↩︎