前言
本文章介绍如何使用基于 AppClient 模型的 Django-Ninja API 鉴权机制。
这也是上次说的中台项目衍生物
中台项目相关的文章,我大概还会再写一篇
这个系列的文章注定是没什么人看的,毕竟还是小众了一些
不过我还是得写,没有读者也要记录,以后需要的时候就能用上
PS: 本文基于使用 DjangoStarter 框架,默认读者具备 DjangoStarter 3.0 以上版本的项目结构、基础设施
限于篇幅关系,本文无法贴出全部代码,有兴趣的同学可以在公众号后台回复「API鉴权」获取完整代码
概述
这套鉴权机制提供了以下功能:
- 多种认证方式:支持查询参数、请求头和 Bearer Token 三种方式
- IP 白名单:支持单个 IP 和 CIDR 网段限制
- 权限范围控制:基于 scopes 的细粒度权限管理
- 状态检查:自动验证 AppClient 的启用状态
- 安全性:包含完整的认证和授权流程
搭配官方文档食用更佳哦~
https://django-ninja.dev/guides/authentication/
使用方法
为了能有个直观的体验
我先介绍使用方法
后面再来讲实现
1. 创建 AppClient
首先在 Django Admin 或通过代码创建一个 AppClient:- from apps.app.models import AppClient
- app_client = AppClient.objects.create(
- app_id="my-app",
- app_name="我的应用",
- secret_key="your-secret-key-here",
- allowed_ips="192.168.1.0/24,10.0.0.1", # 可选:IP 限制
- scopes="project:read,project:write,user:read", # 可选:权限范围
- status=AppClient.Status.ACTIVE
- )
复制代码 2. 在 API 中使用认证
- from ninja import Router
- from core.authentication import (
- api_key_auth,
- api_key_header_auth,
- api_key_bearer_auth,
- require_app_client_scopes,
- AppClientScopes,
- )
- router = Router()
- # 使用查询参数认证
- @router.get("/data", auth=api_key_auth)
- def get_data(request):
- app_client = request.auth
- return {"data": "success", "client": app_client.app_name}
- # 使用请求头认证
- @router.get("/info", auth=api_key_header_auth)
- def get_info(request):
- return {"info": "success"}
- # 使用 Bearer Token 认证
- @router.get("/status", auth=api_key_bearer_auth)
- def get_status(request):
- return {"status": "success"}
复制代码 3. 添加权限控制
- # 需要特定权限
- @router.get("/projects", auth=app_client_api_key)
- @require_app_client_scopes([AppClientScopes.PROJECT_READ])
- def list_projects(request):
- return {"projects": []}
- # 需要多个权限
- @router.post("/projects", auth=app_client_api_key)
- @require_app_client_scopes([AppClientScopes.PROJECT_WRITE, AppClientScopes.USER_READ])
- def create_project(request):
- return {"message": "项目创建成功"}
- # 手动检查权限
- @router.get("/custom-check", auth=app_client_api_key)
- def custom_permission_check(request):
- from core.authentication import AppClientScopeChecker
-
- if AppClientScopeChecker.has_any_scope(request, [AppClientScopes.PROJECT_READ, AppClientScopes.PROJECT_WRITE]):
- return {"access": "granted"}
- else:
- return {"access": "denied"}
复制代码 认证方式
1. 查询参数认证 (APIKeyQuery)
使用方式:- GET /api/endpoint?api_key=your-secret-key
复制代码 代码示例:- from core.authentication import api_key_auth
- @router.get("/data", auth=api_key_auth)
- def get_data(request):
- return {"data": "success"}
复制代码 2. 请求头认证 (APIKeyHeader)
使用方式:- GET /api/endpoint
- X-API-Key: your-secret-key
复制代码 代码示例:- from core.authentication import api_key_header_auth
- @router.get("/data", auth=api_key_header_auth)
- def get_data(request):
- return {"data": "success"}
复制代码 3. Bearer Token 认证 (HttpBearer)
使用方式:- GET /api/endpoint
- Authorization: Bearer your-secret-key
复制代码 代码示例:- from core.authentication import api_key_bearer_auth
- @router.get("/data", auth=api_key_bearer_auth)
- def get_data(request):
- return {"data": "success"}
复制代码 权限范围 (Scopes) 系统
事先说明,scopes 方式只是实现起来最简单,但不代表最合适
毕竟在 Django 框架中,本来就有一套权限体系了,我也有想过接入那套权限体系,可以很方便地在后台编辑每个应用的权限
不过因为实现起来会稍微麻烦一点点,为了快速上线,我就先自己搞了个 scopes 系统了~
后续肯定是要试试 接入 Django 权限体系 的~!
权限格式
权限范围使用 resource:action 格式,例如:
- project:read - 项目读取权限
- project:write - 项目写入权限
- user:* - 用户相关所有权限
- * - 超级管理员权限
预定义权限常量
- from core.authentication import AppClientScopes
- # 项目权限
- AppClientScopes.PROJECT_READ # "project:read"
- AppClientScopes.PROJECT_WRITE # "project:write"
- AppClientScopes.PROJECT_DELETE # "project:delete"
- AppClientScopes.PROJECT_ALL # "project:*"
- # 用户权限
- AppClientScopes.USER_READ # "user:read"
- AppClientScopes.USER_WRITE # "user:write"
- AppClientScopes.USER_DELETE # "user:delete"
- AppClientScopes.USER_ALL # "user:*"
- # 超级权限
- AppClientScopes.SUPER_ADMIN # "*"
复制代码 权限检查方法
1. 装饰器方式(推荐)
- from core.authentication import require_app_client_scopes, AppClientScopes
- @router.get("/projects", auth=app_client_api_key)
- @require_app_client_scopes([AppClientScopes.PROJECT_READ])
- def list_projects(request):
- return {"projects": []}
复制代码 2. 手动检查方式
- from core.authentication import AppClientScopeChecker
- @router.get("/data", auth=app_client_api_key)
- def get_data(request):
- # 检查是否具有所有指定权限
- if AppClientScopeChecker.check_scopes(request, [AppClientScopes.PROJECT_READ]):
- return {"data": "success"}
-
- # 检查是否具有任意一个权限
- if AppClientScopeChecker.has_any_scope(request, [AppClientScopes.PROJECT_READ, AppClientScopes.PROJECT_WRITE]):
- return {"data": "partial access"}
-
- # 获取客户端所有权限
- scopes = AppClientScopeChecker.get_client_scopes(request)
- return {"error": "权限不足", "your_scopes": scopes}
复制代码 IP 白名单
配置 IP 限制
在 AppClient 的 allowed_ips 字段中配置允许的 IP 地址:- # 单个 IP
- app_client.allowed_ips = "192.168.1.100"
- # 多个 IP
- app_client.allowed_ips = "192.168.1.100,10.0.0.1,203.0.113.5"
- # CIDR 网段
- app_client.allowed_ips = "192.168.1.0/24,10.0.0.0/8"
- # 混合配置
- app_client.allowed_ips = "192.168.1.100,10.0.0.0/8,203.0.113.0/24"
- # 不限制 IP(留空或 None)
- app_client.allowed_ips = None
复制代码 IP 获取逻辑
系统会按以下优先级获取客户端 IP:
- HTTP_X_FORWARDED_FOR 头部的第一个 IP(适用于代理/负载均衡环境)
- REMOTE_ADDR(直接连接的 IP)
错误处理
认证失败
当认证失败时,会返回 HTTP 401 错误:- {
- "detail": "Unauthorized"
- }
复制代码 权限不足
当权限检查失败时,会返回 HTTP 403 错误:- {
- "detail": "缺少权限: project:write"
- }
复制代码 自定义错误处理
- from core.authentication import AppClientPermissionError
- @router.get("/custom", auth=app_client_api_key)
- def custom_endpoint(request):
- if not some_condition:
- raise AppClientPermissionError("自定义权限错误信息")
- return {"data": "success"}
复制代码 测试 API
项目提供了完整的测试 API,可以用来验证认证和权限功能:- # 测试 API Key 认证
- GET /api/app/test/api-key?api_key=your-secret-key
- # 测试请求头认证
- GET /api/app/test/api-key-header
- X-API-Key: your-secret-key
- # 测试 Bearer Token 认证
- GET /api/app/test/bearer
- Authorization: Bearer your-secret-key
- # 测试权限范围
- GET /api/app/test/scopes/project-read?api_key=your-secret-key
- GET /api/app/test/scopes/project-write?api_key=your-secret-key
- GET /api/app/test/scopes/multiple?api_key=your-secret-key
复制代码 最佳实践
以下是一些最佳实践的建议
无论是使用什么技术、框架,实际开发中都可以参考一下
1. 密钥管理
- 使用强密钥(建议 32 字符以上)
- 定期轮换密钥
- 不要在代码中硬编码密钥
- 使用环境变量或安全的配置管理系统
2. 权限设计
- 遵循最小权限原则
- 使用细粒度的权限范围
- 定期审查和清理不必要的权限
- 为不同的应用场景创建不同的 AppClient
3. IP 限制
- 在生产环境中启用 IP 白名单
- 使用 CIDR 网段而不是单个 IP(便于扩展)
- 考虑 CDN 和代理的 IP 转发
4. 监控和日志
- import logging
- logger = logging.getLogger(__name__)
- @router.get("/sensitive-data", auth=app_client_api_key)
- @require_app_client_scopes([AppClientScopes.PROJECT_READ])
- def get_sensitive_data(request):
- app_client = request.auth
- logger.info(f"AppClient {app_client.app_id} accessed sensitive data")
- return {"data": "sensitive information"}
复制代码 集成到现有 API
添加路由
在 config/apis.py 中添加 app 路由:- from apps.app.apis import router as app_router
- api.add_router('app', app_router)
复制代码 混合认证
这是 django-ninja 提供的新功能
可以在同一个 API 中同时支持多种认证方式:- from ninja.security import django_auth
- from core.authentication import api_key_auth
- # 同时支持 Django 用户认证和 AppClient 认证
- @router.get("/user-data", auth=[django_auth, api_key_auth])
- def get_data(request):
- return {"user": request.user.username}
复制代码 更新主 API 配置
除此之外,还可以在主 API 配置全局认证和其他功能- from django.contrib.admin.views.decorators import staff_member_required
- api = NinjaAPI(
- title=f'{settings.DJANGO_STARTER["project_info"]["name"]} APIs',
- description=settings.DJANGO_STARTER["project_info"]["description"],
- renderer=JSONRespRenderer(),
- csrf=True,
- auth=[django_auth, api_key_auth, api_key_header_auth, api_key_bearer_auth],
- urls_namespace='api',
- docs=Swagger(settings={"persistAuthorization": True}),
- docs_decorator=staff_member_required,
- )
复制代码 这里简单介绍一下
我这段配置:
- 使用 auth=[django_auth, api_key_auth, api_key_header_auth, api_key_bearer_auth] 支持多种认证方式(其实还有一个JWT的,不过我这里没考虑)
- 使用 docs_decorator=staff_member_required, 配置了访问swagger文档必须是已登录的 staff_member
实现
OK,最后再介绍一下具体实现 (应该没人会坚持看到这么后面吧…)
限于篇幅关系,本文无法贴出全部代码,有兴趣的同学可以在公众号后台回复「API鉴权」获取完整代码
模型
首先是 AppClient 模型,代码位于 src/apps/app/models.py 内
除了字段定义,还添加了一些方法用于 获取权限范围列表、获取允许的 IP 地址列表 之类的功能
按照DDD思想来讲,这个模型算是一个 充血模型
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |