API 参考
认证后端 qxj-backend-admin(Django 4.2 + DRF)对外提供的 REST API。本文以 main 交付版代码(config/urls.py 与各 app 的 urls.py)为准, 覆盖认证类接口与全部管理端接口;两个 SDK 的语言级 API 见 JavaScript 前端 SDK 与 Python 后端 SDK。
1. 基础信息
| 项目 | 值 |
|---|---|
| 线上地址 | https://qxj-backend-admin.qxj.nsp.ac.cn |
| 本地开发 | http://127.0.0.1:4607 |
| API 前缀 | /api/v3/(管理端统一再挂 /admin/) |
| 认证头 | Authorization: Bearer {access} |
| 数据格式 | 业务接口 JSON;SM2 握手与加密登录为 application/octet-stream |
| 字符编码 | UTF-8;二进制帧内 UUID 为 ASCII、数字字段大端 |
| 时间 | Unix 秒级时间戳(帧结构中的有效期为 8 字节大端整数) |
2. 通用约定
2.1 认证与权限
- 业务认证类:DRF 全局
DEFAULT_AUTHENTICATION_CLASSES = BlacklistJWTAuthentication, 即每个请求默认都要通过 NSP-SM token 的外层 SM2 验签、jti 黑名单、sid 撤销、 tv 版本与用户状态检查;内层 HMAC 默认为「有则校验、空则跳过」, 强制设备登录时(VERIFY_INNER_HMAC=true)要求 HMAC 必填。 - 默认权限:
common.permissions.IsAdmin——is_staff或is_superuser才放行; 登录、刷新、握手、短信等公开接口在视图上显式声明AllowAny。 - 写操作收窄:角色、用户角色关联等模块的写操作(增 / 改 / 删)仅 超管 可执行。
2.2 响应格式
项目存在两类响应体,调用方需区分:
① 认证类接口的业务信封(登录 / 登出 / 刷新等):
// 成功
{ "success": true, "data": { /* access / refresh / user 等 */ } }
// 业务失败(HTTP 400 / 403 等)
{ "success": false, "message": "首条校验错误文案" }2
3
4
② 普通 DRF 视图:直接返回序列化数据,无外层包装;字段校验错误为 DRF 默认结构 ({字段名: [消息, ...]}),权限 / 限流等错误为 {"detail": "..."}。 列表接口的分页结构见下节。
2.3 分页
StandardPagination(PageNumberPagination):
| 参数 | 说明 |
|---|---|
page | 页码,从 1 开始 |
page_size | 每页条数,默认 10,最大 100 |
{ "count": 123, "next": "https://...?page=2", "previous": null, "results": [ /* ... */ ] }页码非法或超出末页不抛 404,返回 HTTP 200 + 空 results。 注意:device_types(设备类型)接口显式关闭分页,一次返回全部数据。
2.4 过滤、搜索与排序
列表接口统一启用 BoundedDjangoFilterBackend + Search + Ordering:
- 精确 / 范围过滤:如用户按
status、is_active;设备按status、eqp_type; 安全日志按log_type、result、level、user、ip与日期范围; - 搜索:
?search=关键词(用户、设备等按名称 / 编号模糊匹配); - 排序:
?ordering=field,-前缀为降序。
2.5 限流
| 作用域 | 默认阈值 |
|---|---|
| 匿名用户 | 60 次 / 分钟 |
| 登录用户 | 600 次 / 分钟 |
| 短信发送(sms) | 5 次 / 分钟 |
| 人机校验(captcha) | 20 次 / 分钟 |
客户端 IP 直接取 TCP 对端地址,不信任 X-Forwarded-Fox(TRUSTED_PROXY_COUNT 默认 0),防止伪造头绕过限流。触发限流返回 HTTP 429 + {"detail": "请求过于频繁..."}。
2.6 异常处理
全局 drf_exception_handler 统一兜底:
- OverflowError、Django
ValidationError、ORM 强转ValueError先转为 400; - DRF 已知异常走默认处理;
- 生产环境未预期异常记日志后返回
500 {"detail": "服务器开小差了,请稍后重试"}, 不向前端泄漏堆栈;DEBUG 模式才返回调试详情。
3. 认证类接口
3.1 登录(双模式)
POST /api/v3/user/login/,按 Content-Type 自动选择模式。
3.1.1 JSON 明文模式
需 ALLOW_JSON_LOGIN=true(默认 false,此时返回 HTTP 403)。
POST /api/v3/user/login/
Content-Type: application/json
{
"username": "zhouyueyang",
"password": "明文密码",
"verification_code": "",
"device_id": "",
"position": "",
"captcha_verify_param": ""
}2
3
4
5
6
7
8
9
10
11
| 字段 | 必填 | 说明 |
|---|---|---|
username / password | 是 | 账号密码 |
verification_code | 否 | 短信 / 邮箱验证码(启用验证码场景时必填,手机与邮箱同一码) |
device_id | 否 | 设备 UUID;加密模式下须等于帧头 sender |
position | 否 | 定位,经度,纬度 |
captcha_verify_param | 否 | 人机验证通过票据 |
成功(HTTP 200):
{
"success": true,
"data": {
"access": "NSP-SM JWT",
"refresh": "NSP-SM JWT",
"user": {
"id": 1,
"username": "zhouyueyang",
"real_name": "周月阳",
"email": "...",
"phone": "...",
"is_staff": true,
"is_superuser": false,
"status": "active",
"status_display": "正常",
"expires_at": null,
"date_joined": "2026-09-01T08:00:00Z",
"roles": [{ "id": 2, "name": "管理员", "role_type": "admin", "role_type_display": "管理员" }]
}
}
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
业务失败(HTTP 400):{"success": false, "message": "用户名或密码错误"}。
3.1.2 加密信封模式(甲方主用)
Content-Type: application/octet-stream(或 x-qxj-encrypted),需 ALLOW_DEVICE_LOGIN=true(默认开启)。请求体是一帧完整的 64B 标准信封:
- 帧头
main_cmd=2(加密完保)、sub_cmd=0x0001(上行)、enc_auth=0x40(SM4-GCM); - 数据域 = 「登录 JSON 对象」经设备会话密钥 SM4-GCM 加密后的密文 + 16B GCM tag;
- 服务器处理顺序:拆帧 → 按帧头
sender(IDA)查会话密钥 → 解密 → 校验 明文 JSON 的device_id必须等于帧头 sender → 账密 / 验证码 / 设备有效性 / 时间规则 / 地理围栏逐项校验。
成功响应同样是 application/octet-stream:用同一会话密钥加密登录结果, 封成下行帧(sub_cmd=0x8002,帧头 sender 为服务器 IDB)。 此外对请求密文做 SHA-256 + cache SET NX 的重放防护:同一密文在成功受理后 冷却 5 分钟,重复提交直接拒绝。
加解密与封帧在鸿蒙端由原生安全区完成(业务网页经 JSBridge 触发,见 前端 SDK);业务后端如需同样处理 信封,见 后端 SDK。
3.2 登出
POST /api/v3/user/logout/
Authorization: Bearer {access}
Content-Type: application/json
{ "refresh": "NSP-SM refresh token" }2
3
4
5
refresh必填:其 jti 写入黑名单(TTL = 剩余有效期,最少 60s),并撤销整个 sid (同一登录会话下的全部 token 一并失效);- 请求头中的 access jti 也同时拉黑;
- 返回:
{"success": true, "message": "已成功退出登录"}。
3.3 刷新 access
POST /api/v3/token/refresh/
Content-Type: application/json
{ "refresh": "NSP-SM refresh token" }2
3
4
服务端依次校验:SM2 外层验签 → token_type 必须为 refresh → jti 不在黑名单 → sid 未撤销 → 用户仍有效 → tv 版本匹配 → 设备与会话密钥仍存在。
成功(不轮换 refresh):
{ "success": true, "data": { "access": "新的 NSP-SM access token" } }3.4 人机校验与短信验证码
| 方法 / 路径 | 说明 |
|---|---|
POST /api/v3/user/captcha/verify/ | 校验人机票据(AllowAny,限流 20/min) |
POST /api/v3/sms/send_code/ | 发送登录验证码(手机短信 / 邮箱同码),限流 5/min;入参含 username、captcha_verify_param |
3.5 当前用户
| 方法 / 路径 | 说明 |
|---|---|
GET /api/v3/users/me/ | 当前登录用户资料(需登录) |
PATCH /api/v3/users/me/ | 修改本人资料 / 密码(入参 old_password、new_password) |
4. NSP-SM Token 结构
双 token 均为三段式 JWT,但签名算法是自定义的 NSP-SM(非标准 HS256/RS256, jwt.io 无法验签):
base64url(header) . base64url(payload) . base64url(signature)- header:
{"alg": "NSP-SM", "typ": "JWT"}; - payload:
- 标准 / 自定义 claims:
jti(唯一 ID,黑名单依据)、iat、exp、token_type(access / refresh)、sid(会话 ID,整会话撤销依据)、tv(令牌版本); custom:{ "device_id": "设备UUID", "user_id": 1, "exp_s": 1728000000, "hmac": "内层SM3-HMAC hex" },其中exp_s必须与外层exp相等;
- 标准 / 自定义 claims:
- signature:对
header_b64.payload_b64先 SM3 摘要、再用激活的服务器 SM2 私钥签名(r‖s,64 字节); - 内层 hmac:
SM3-HMAC(设备会话密钥, UTF8(device_id) ‖ uint64BE(user_id) ‖ uint64BE(exp))。
有效期(config/settings/base.py):
| Token | 默认有效期 | 配置项 |
|---|---|---|
| access | 12 小时 | ACCESS_TOKEN_LIFETIME_HOURS |
| refresh | 7 天 | REFRESH_TOKEN_LIFETIME_DAYS |
ROTATE_REFRESH_TOKENS=false(刷新只换 access,refresh 不轮换)。
Token 校验返回码(Python SDK 口径)
业务后端通过 qxj-backend-sdk 验证 token 时 得到的 result_code:
| code | 含义 |
|---|---|
| 0 | 完全通过(外层 SM2 + 内层 HMAC) |
| 1 | 外层通过、内层跳过(device_id 为空或显式 skip_hmac) |
| -1 | token 为空 |
| -2 | 三段解析失败 |
| -3 | 外层 SM2 验签失败(含 alg 不是 NSP-SM) |
| -4 | 已过期(exp 缺失 / 非法也归此码,fail-closed) |
| -5 | custom 缺失 / 非法(含 exp_s 与 exp 不一致) |
| -6 | 内层 HMAC 失败 |
| -7 | 无会话密钥(查无密钥 / hmac 空 / 密钥格式非法) |
| -8 | Redis 异常 |
| -99 | 未知错误 |
JavaScript SDK 的
verifyJwt使用另一套正数编码(0/1/10/11/12/20/21/30/31/32/33/99), 语义一一对应,详见 前端 SDK 错误码。
5. SM2 握手接口
设备首次接入须完成四步握手,协商出设备会话密钥(后续内层 HMAC 与 SM4-GCM 的密钥根)。 网络层承载两个 HTTP 往返,均为 application/octet-stream:
POST /api/v3/keymgr/sm2/build_resp/
Content-Type: application/octet-stream
[64B 帧头 + INIT 数据域,共 280B]
POST /api/v3/keymgr/sm2/build_token/
Content-Type: application/octet-stream
[64B 帧头 + ACK 数据域,共 98B]2
3
4
5
6
7
8
9
- 成功分别返回 RESP(162B)/ TOKEN(146B)二进制帧;
- TOKEN 阶段成功后,会话密钥发布到 cache 键
sm2:device_session_key:{device_id}, TTL 7 天(物理 Redis 键为qxj:1:sm2:device_session_key:{device_id}); 握手中途上下文sm2:session:{uuid}TTL 10 分钟; - 失败返回 HTTP 200 + 报警帧(方向位 1:
0x8002/0x8003/0x8004)。
握手的密码学细节(INIT/RESP/ACK/TOKEN 数据域布局、随机数与签名校验)见 SM2 密钥协商;64B 帧头逐字段布局见 后端 SDK 信封结构。
6. 管理端接口
统一前缀 /api/v3/admin/,默认权限 IsAdmin(特殊写操作仅超管);除特别说明外均为 DefaultRouter 生成的标准 RESTful 路径(带尾斜杠),列表支持分页 / 过滤 / 搜索。
6.1 用户管理
| 方法 / 路径 | 说明 |
|---|---|
GET /admin/users/ | 用户分页列表(可按 search、status、is_active 过滤) |
POST /admin/users/ | 新建用户 |
GET /admin/users/{id}/ | 用户详情 |
PATCH /admin/users/{id}/ | 修改用户(PUT 全量更新) |
DELETE /admin/users/{id}/ | 删除用户(不能删自己、不能删最后一个超管;普通管理员不能操作超管) |
POST /admin/users/{id}/set_password/ | 管理员重置指定用户密码 |
6.2 设备体系
| 方法 / 路径 | 说明 |
|---|---|
GET/POST /admin/devices/ | 设备列表 / 注册(过滤 status、eqp_type) |
GET/PATCH/DELETE /admin/devices/{id}/ | 设备详情 / 修改 / 删除 |
GET/POST /admin/device_types/ | 设备类型(不分页) |
PATCH/DELETE /admin/device_types/{id}/ | 类型修改 / 删除 |
GET/POST /admin/device_public_keys/ | 设备公钥登记(过滤 device、status、key_type) |
PATCH/DELETE /admin/device_public_keys/{id}/ | 公钥修改 / 删除 |
POST /admin/device_public_keys/import_file/ | 批量导入:multipart 上传 .dat,行格式 device_id#十进制秒#128hex公钥;返回 {message, imported, skipped, errors} |
POST /admin/device_public_keys/{id}/deactivate/ | 作废公钥(软删 status=destroyed,并联动撤销对应会话密钥) |
6.3 角色与三关联表
| 方法 / 路径 | 说明 |
|---|---|
GET/POST /admin/roles/ | 角色列表 / 新建(写操作仅超管) |
PATCH/DELETE /admin/roles/{id}/ | 修改 / 删除角色 |
GET/POST/DELETE /admin/user_roles/ | 用户—角色关联(写操作仅超管;变更后信号重算用户 is_staff / is_superuser) |
GET/POST/DELETE /admin/role_geofences/ | 角色—围栏关联 |
GET/POST/DELETE /admin/role_time_rules/ | 角色—时间规则关联 |
6.4 围栏与时间规则
| 方法 / 路径 | 说明 |
|---|---|
GET/POST /admin/geofences/ | 地理围栏(过滤 coord_system) |
GET/PATCH/DELETE /admin/geofences/{id}/ | 围栏详情 / 修改 / 删除 |
GET/POST /admin/time_rules/ | 时间访问规则(过滤 mode、is_active) |
GET/PATCH/DELETE /admin/time_rules/{id}/ | 规则详情 / 修改 / 删除 |
6.5 安全审计日志
| 方法 / 路径 | 说明 |
|---|---|
GET /admin/security_logs/ | 日志分页查询(按 log_type、result、level、user、ip、日期范围过滤) |
GET /admin/security_logs/{id}/ | 日志详情 |
DELETE /admin/security_logs/{id}/ | 删除日志(仅超管;接口不提供新建 / 修改) |
GET /admin/security_logs/statistics/ | 分类统计 |
GET /admin/security_logs/statistics/trend/ | 趋势统计 |
GET /admin/security_logs/export/ | 导出 CSV(上限 10000 条) |
6.6 服务器密钥
| 方法 / 路径 | 说明 |
|---|---|
GET /admin/server_keys/active_qrcode/ | 当前激活 ServerKey 的二维码信息:server_id、algorithm、key_type、public_key(128 hex 大写)、timestamp、qrcode_string(server_id#timestamp#public_key)、is_active、created_at;无激活密钥时 404 |
终端扫码该二维码即可绑定服务器身份与 SM2 公钥;管理员也可用 python -m tools.setup qrcode 从命令行生成同样内容。
7. 典型调用时序
8. 延伸阅读
- JavaScript 前端 SDK:国密算法、verifyJwt、JSBridge Promise 封装与业务前端真实接入
- Python 后端 SDK:verify_access_token、SM4-GCM 信封与 DRF 认证类接入
- SM2 密钥协商:握手密码学细节
- 加密登录:信封与多层加密
- 访问控制:CoAC、围栏与时间规则