返回文章列表文章索引阅读全文
STARBLOG / 阅读手记
Django

Implementing an application authentication mechanism similar to Tencent or Alibaba Cloud in django-ninja.

Django-Ninja API authentication mechanism? Isn't it just another authentication system based on AppClient, supporting query parameters, headers, and Bearer Token authentication, plus IP whitelisting and scopes permission control? The code is complex enough, convoluted enough?

曦远 (DealiAxy)约 68 分钟
目录37 个章节

Preface

This article introduces how to use the Django-Ninja API authentication mechanism based on the AppClient model.

This is also a derivative of the mid-platform project mentioned previously.

I will probably write another article related to the mid-platform project.

This series of articles is destined to be read by few people, as it is still somewhat niche.

However, I still need to write it. Even without readers, I want to document it so that I can use it in the future when needed.

PS: This article is based on the use of the DjangoStarter framework, assuming the reader is familiar with the project structure and infrastructure of DjangoStarter version 3.0 or above.

Due to space limitations, this article cannot include all the code. Interested readers can reply "API authentication" in the background of the WeChat public account to get the complete code.

Overview

This authentication mechanism provides the following features:

  • ==Multiple authentication methods==: Supports query parameters, request headers, and Bearer Token
  • ==IP Whitelist==: Supports individual IP addresses and CIDR network ranges
  • ==Scope-based permission control==: Fine-grained permission management based on scopes
  • ==Status check==: Automatically verifies the enabled status of AppClient
  • ==Security==: Includes a complete authentication and authorization flow

It goes best with the official documentation~

https://django-ninja.dev/guides/authentication/

Usage

To give you an intuitive experience

I'll introduce the usage first

Then I'll explain the implementation.

1. Create AppClient

First, create an AppClient either in Django Admin or through code:

from apps.app.models import AppClient

app_client = AppClient.objects.create(
    app_id="my-app",
    app_name="My App",
    secret_key="your-secret-key-here",
    allowed_ips="192.168.1.0/24,10.0.0.1",  # Optional: IP restriction
    scopes="project:read,project:write,user:read",  # Optional: scope
    status=AppClient.Status.ACTIVE
)

2. Using Authentication in the 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()

Authentication with Query Parameters

@router.get("/data", auth=api_key_auth) def get_data(request): app_client = request.auth return {"data": "success", "client": app_client.app_name}

Using Request Header Authentication

@router.get("/info", auth=api_key_header_auth) def get_info(request): return {"info": "success"}

Using Bearer Token Authentication

@router.get("/status", auth=api_key_bearer_auth) def get_status(request): return {"status": "success"}

### 3. Adding Permission Control

```python
# Requires Specific Permissions
@router.get("/projects", auth=app_client_api_key)
@require_app_client_scopes([AppClientScopes.PROJECT_READ])
def list_projects(request):
    return {"projects": []}
# Requires Multiple Permissions
```python
@router.post("/projects", auth=app_client_api_key)
@require_app_client_scopes([AppClientScopes.PROJECT_WRITE, AppClientScopes.USER_READ])
def create_project(request):
    return {"message": "Project created successfully"}

Manual Permission Check

@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"}
## Authentication Methods
### 1. Query Parameter Authentication (APIKeyQuery)

**Usage:**

```bash
GET /api/endpoint?api_key=your-secret-key

Code Example:

from core.authentication import api_key_auth


@router.get("/data", auth=api_key_auth)
def get_data(request):
   return {"data": "success"}

2. API Key Header Authentication (APIKeyHeader)

Usage:

GET /api/endpoint
X-API-Key: your-secret-key

Code Example:

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 Authentication (Bearer Token 认证)

Usage:

GET /api/endpoint
Authorization: Bearer your-secret-key

Code Example:

from core.authentication import api_key_bearer_auth


@router.get("/data", auth=api_key_bearer_auth)
def get_data(request):
   return {"data": "success"}

Permission Scopes System

To clarify upfront, the scopes approach is simply the easiest to implement, but it doesn't mean it's the most appropriate.

After all, Django already has its own built-in permission system. I've considered integrating that system, which would allow for convenient editing of permissions for each application in the admin panel.

However, since that implementation would be slightly more complicated, and for the sake of getting things launched quickly, I initially went with creating my own scopes system.

I definitely plan to try ==integrating the Django permission system== in the future!

Permission Format

Permissions use the resource:action format. For example:

  • project:read - Project read permissions
  • project:write - Project write permissions
  • user:* - All user-related permissions
  • * - Super administrator permissions

Predefined Permission Constants

from core.authentication import AppClientScopes

Project Permissions

AppClientScopes.PROJECT_READ # "project:read" AppClientScopes.PROJECT_WRITE # "project:write" AppClientScopes.PROJECT_DELETE # "project:delete" AppClientScopes.PROJECT_ALL # "project:*"

User Permissions

AppClientScopes.USER_READ # "user:read" AppClientScopes.USER_WRITE # "user:write" AppClientScopes.USER_DELETE # "user:delete" AppClientScopes.USER_ALL # "user:*"

Super Privileges

AppClientScopes.SUPER_ADMIN # "*"

### Permission Checking Methods

#### 1. Decorator Approach (Recommended)

```python
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. Manual Check Approach

from core.authentication import AppClientScopeChecker

@router.get("/data", auth=app_client_api_key)
def get_data(request):
    # Check if all specified permissions are present
    if AppClientScopeChecker.check_scopes(request, [AppClientScopes.PROJECT_READ]):
        return {"data": "success"}
    
    # Check if any one of the permissions is present
    if AppClientScopeChecker.has_any_scope(request, [AppClientScopes.PROJECT_READ, AppClientScopes.PROJECT_WRITE]):
        return {"data": "partial access"}
    
    # Get all client permissions
    scopes = AppClientScopeChecker.get_client_scopes(request)
    return {"error": "Insufficient permissions", "your_scopes": scopes}

IP Whitelist

Configure IP Restrictions

Configure the allowed IP addresses in the allowed_ips field of AppClient:

# Single IP
app_client.allowed_ips = "192.168.1.100"
# Multiple IPs
app_client.allowed_ips = "192.168.1.100,10.0.0.1,203.0.113.5"
# CIDR Subnet
app_client.allowed_ips = "192.168.1.0/24,10.0.0.0/8"
# Mixed Configuration
app_client.allowed_ips = "192.168.1.100,10.0.0.0/8,203.0.113.0/24"
# No IP restrictions (leave empty or None)
app_client.allowed_ips = None

IP Acquisition Logic

The system will acquire the client IP address according to the following priority:

  1. The first IP from the HTTP_X_FORWARDED_FOR header (applicable in proxy/load balancing environments)
  2. REMOTE_ADDR (the IP of the direct connection)

Error Handling

Authentication Failed

When authentication fails, an HTTP 401 error will be returned:

{
    "detail": "Unauthorized"
}

Insufficient Permissions

When a permission check fails, an HTTP 403 error is returned:

{
    "detail": "缺少权限: project:write"
}

Custom Error Handling

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"}

Test API

The project provides complete test APIs that can be used to verify authentication and permission features:

# Test API Key Authentication
GET /api/app/test/api-key?api_key=your-secret-key
# Test Header Authentication
GET /api/app/test/api-key-header
X-API-Key: your-secret-key
# Testing Bearer Token Authentication  
GET /api/app/test/bearer  
Authorization: Bearer your-secret-key
# Test Permission Scopes
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
## Best Practices

Here are some recommendations for best practices

Regardless of the technology or framework used, these can be referenced during actual development
### 1. Key Management

- Use strong keys (recommended 32 characters or more)
- Rotate keys regularly
- Do not hard-code keys in the code
- Use environment variables or a secure configuration management system
### 2. Permission Design

- Adhere to the **Least Privilege Principle**
- Use **fine-grained permission scopes**
- Regularly review and clean up unnecessary permissions
- Create different **AppClients** for different application scenarios
### 3. IP Restrictions

- Enable IP whitelist in the production environment
- Use CIDR ranges instead of individual IPs (for easier scaling)
- Consider CDN and proxy IP forwarding
### 4. Monitoring and Logging

```python
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"}

Integrate into Existing API

Adding Routes

Add the app route in config/apis.py:

from apps.app.apis import router as app_router

api.add_router('app', app_router)

Hybrid Authentication

This is a new functionality provided by django-ninja

It can support multiple authentication methods simultaneously within the same API:

from ninja.security import django_auth
from core.authentication import api_key_auth

Simultaneously Supporting Django User Authentication and AppClient Authentication

@router.get("/user-data", auth=[django_auth, api_key_auth]) def get_data(request): return {"user": request.user.username}

### Update Main API Configuration

In addition to this, you can configure global authentication and other features in the main API configuration.

```python
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,
)

Here is a brief introduction:

My configuration:

  • Uses auth=[django_auth, api_key_auth, api_key_header_auth, api_key_bearer_auth] to support multiple authentication methods (there is also a JWT one, but I haven't considered it here).
  • Uses docs_decorator=staff_member_required, to configure that accessing the Swagger documentation requires a logged-in staff_member (职员成员).

Implementation

OK, finally, let me introduce the specific implementation (I doubt anyone has scrolled this far…)

Due to space constraints, this article cannot include all the code. Interested readers can reply with "API authentication (API鉴权)" in the background of our official account to get the complete code.

Model

First is the AppClient model, with its code located in src/apps/app/models.py.

In addition to field definitions, several methods have been added for functionalities such as ==getting the list of permission scopes== and ==getting the list of allowed IP addresses==.

According to DDD principles, this model can be considered a ==rich domain model== 😃

import uuid
from django.db import models
from django.core.validators import RegexValidator
from django_starter.db.models import ModelExt

class AppClient(ModelExt):
    """Application Client Model for managing third-party application access permissions"""

    class Status(models.TextChoices):
        ACTIVE = 'active', '启用'
        INACTIVE = 'inactive', '禁用'

    id = models.BigAutoField(
        primary_key=True,
        verbose_name='主键ID',
        help_text='系统自动生成的唯一标识符',
        db_comment='物理主键,自增ID'
    )

    guid = models.UUIDField(
        default=uuid.uuid4,
        unique=True,
        editable=False,
        verbose_name='全局唯一标识',
        help_text='系统自动生成的UUID,用作逻辑主键',
        db_comment='逻辑主键,GUID'
    )

    app_id = models.CharField(
        max_length=100,
        unique=True,
        verbose_name='应用标识',
        help_text='用于标识调用方系统的唯一ID,建议使用有意义的命名',
        db_comment='应用标识,用于标识调用方系统',
        validators=[
            RegexValidator(
                regex=r'^[a-zA-Z0-9_-]+$',
                message='应用标识只能包含字母、数字、下划线和连字符'
            )
        ]
    )

    app_name = models.CharField(
        max_length=200,
        verbose_name='应用名称',
        help_text='调用方应用的显示名称或备注信息',
        db_comment='应用名称,调用方的名称或备注信息'
    )

    secret_key = models.CharField(
        max_length=100,
        verbose_name='密钥',
        help_text='用于API签名验证的密钥,请妥善保管',
        db_comment='密钥,用于签名验证',
    )

    allowed_ips = models.TextField(
        blank=True,
        null=True,
        verbose_name='允许访问的IP',
        help_text='允许访问的IP地址列表,多个IP用逗号分隔,留空表示不限制',
        db_comment='允许访问的 IP 列表,逗号分隔'
    )

    scopes = models.TextField(
        blank=True,
        null=True,
        verbose_name='权限范围',
        help_text='应用的权限范围,格式如:project:read,project:write',
        db_comment='权限范围,如 project:read,project:write'
    )

    status = models.CharField(
        max_length=10,
        choices=Status.choices,
        default=Status.ACTIVE,
        verbose_name='状态',
        help_text='应用的启用状态',
        db_comment='启用/禁用状态'
    )

    class Meta:
        db_table = 'app_client'
        verbose_name = '应用客户端'
        verbose_name_plural = '应用客户端'
        ordering = ['-create_time']
        indexes = [
            models.Index(fields=['app_id']),
            models.Index(fields=['status']),
            models.Index(fields=['create_time']),
        ]

    def __str__(self):
        return f'{self.app_name} ({self.app_id})'

    def is_active(self):
        """Check if the application is in an active state"""
        # todo 为什么这个 is_deleted 会变成 str 啊??
        # return self.status == self.Status.ACTIVE and not self.is_deleted
        return self.status == self.Status.ACTIVE

    def get_scopes_list(self):
        """Get the list of permission scopes"""
        if not self.scopes:
            return []
        return [scope.strip() for scope in self.scopes.split(',') if scope.strip()]

    def get_allowed_ips_list(self):
        """Get the list of allowed IP addresses"""
        if not self.allowed_ips:
            return []
        return [ip.strip() for ip in self.allowed_ips.split(',') if ip.strip()]

    def save(self, *args, **kwargs):
        """Override save method to add custom logic"""
        # Ensure app_name is not empty
        if not self.app_name:
            self.app_name = self.app_id

        super().save(*args, **kwargs)

Test Interfaces

Add some test interfaces for easier debugging later

The code is located in src/apps/app/apis/client/apis.py

from typing import List
from ninja import Router
from django.http import HttpRequest
from core.authentication import (
    api_key_auth,
    api_key_header_auth,
    api_key_bearer_auth,
    require_app_client_scopes,
    AppClientScopes,
    AppClientScopeChecker,
)
from apps.app.models import AppClient
from .schemas import AppClientSchema, AppClientCreateSchema

router = Router(tags=['AppClient API'])

@router.get("/test/api-key", auth=api_key_auth)
def test_api_key_auth(request: HttpRequest):
    """测试API Key认证(查询参数方式)
    
    使用方式: GET /api/app/test/api-key?api_key=your_secret_key
    """
    app_client = request.auth
    return {
        "message": "API Key认证成功",
        "app_id": app_client.app_id,
        "app_name": app_client.app_name,
        "scopes": app_client.get_scopes_list(),
    }


@router.get("/test/api-key-header", auth=api_key_header_auth)
def test_api_key_header_auth(request: HttpRequest):
    """测试API Key认证(请求头方式)
    
    使用方式: GET /api/app/test/api-key-header
    Headers: X-API-Key: your_secret_key
    """
    app_client = request.auth
    return {
        "message": "API Key Header认证成功",
        "app_id": app_client.app_id,
        "app_name": app_client.app_name,
        "scopes": app_client.get_scopes_list(),
    }


@router.get("/test/bearer", auth=api_key_bearer_auth)
def test_bearer_auth(request: HttpRequest):
    """测试Bearer Token认证
    
    使用方式: GET /api/app/test/bearer
    Headers: Authorization: Bearer your_secret_key
    """
    app_client = request.auth
    return {
        "message": "Bearer Token认证成功",
        "app_id": app_client.app_id,
        "app_name": app_client.app_name,
        "scopes": app_client.get_scopes_list(),
    }


@router.get("/test/scopes/project-read", auth=api_key_auth)
@require_app_client_scopes([AppClientScopes.PROJECT_READ])
def test_project_read_scope(request: HttpRequest):
    """测试项目读取权限
    
    需要权限: project:read
    """
    return {
        "message": "项目读取权限验证成功",
        "required_scope": AppClientScopes.PROJECT_READ,
        "client_scopes": AppClientScopeChecker.get_client_scopes(request),
    }


@router.get("/test/scopes/project-write", auth=api_key_auth)
@require_app_client_scopes([AppClientScopes.PROJECT_WRITE])
def test_project_write_scope(request: HttpRequest):
    """测试项目写入权限
    
    需要权限: project:write
    """
    return {
        "message": "项目写入权限验证成功",
        "required_scope": AppClientScopes.PROJECT_WRITE,
        "client_scopes": AppClientScopeChecker.get_client_scopes(request),
    }


@router.get("/test/scopes/multiple", auth=api_key_auth)
@require_app_client_scopes([AppClientScopes.PROJECT_READ, AppClientScopes.USER_READ])
def test_multiple_scopes(request: HttpRequest):
    """测试多个权限要求
    
    需要权限: project:read AND user:read
    """
    return {
        "message": "多权限验证成功",
        "required_scopes": [AppClientScopes.PROJECT_READ, AppClientScopes.USER_READ],
        "client_scopes": AppClientScopeChecker.get_client_scopes(request),
    }


@router.get("/test/scopes/any", auth=api_key_auth)
def test_any_scope(request: HttpRequest):
    """测试任意权限检查
    
    检查是否具有项目或用户相关的任意权限
    """
    has_project_access = AppClientScopeChecker.has_any_scope(
        request, [AppClientScopes.PROJECT_READ, AppClientScopes.PROJECT_WRITE]
    )
    has_user_access = AppClientScopeChecker.has_any_scope(
        request, [AppClientScopes.USER_READ, AppClientScopes.USER_WRITE]
    )
    
    return {
        "message": "权限检查完成",
        "has_project_access": has_project_access,
        "has_user_access": has_user_access,
        "client_scopes": AppClientScopeChecker.get_client_scopes(request),
    }


@router.get("/info", auth=api_key_auth)
def get_client_info(request: HttpRequest):
    """获取当前认证的AppClient信息"""
    app_client = request.auth
    return {
        "app_id": app_client.app_id,
        "app_name": app_client.app_name,
        "status": app_client.status,
        "scopes": app_client.get_scopes_list(),
        "allowed_ips": app_client.get_allowed_ips_list(),
        "create_time": app_client.create_time,
    }


@router.get("/clients", auth=api_key_auth)
@require_app_client_scopes([AppClientScopes.SUPER_ADMIN])
def list_app_clients(request: HttpRequest) -> List[dict]:
    """列出所有AppClient(需要超级管理员权限)
    
    需要权限: *
    """
    clients = AppClient.objects.filter(is_deleted=False)
    return [
        {
            "app_id": client.app_id,
            "app_name": client.app_name,
            "status": client.status,
            "scopes": client.get_scopes_list(),
            "create_time": client.create_time,
        }
        for client in clients
    ]

Management Commands

For convenience, I've added two more commands

As the name suggests, ==Create a new AppClient== and ==List all==

  • src/apps/app/management/commands/create_app_client.py
  • src/apps/app/management/commands/list_app_clients.py

Due to space limitations, I can't include all the code here. If you're interested, you can reply with "API Authentication (API鉴权)" in the backend of the official account to get the complete code.

Core Logic

Regarding the core logic of authentication, I have created a separate core package to store it.

The directory structure is as follows:

 core
 ├─ exceptions
 │  ├─ __init__.py
 │  └─ permissions.py
 ├─ authentication
 │  ├─ __init__.py
 │  ├─ README.md
 │  ├─ permissions.py
 │  ├─ app.py
 │  └─ api_key.py
 └─ __init__.py

The most critical code consists of these authentication instances in src/core/authentication/app.py.

The code contains detailed comments, making it easy to understand at a glance.

If you find this still a bit complex, I will later introduce an extremely simple implementation.

class AppClientAuthMixin:
    """AppClient authentication mixin class, providing common authentication logic"""
    
    def _get_client_ip(self, request: HttpRequest) -> str:
        """Get the client's real IP address"""
        x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
        if x_forwarded_for:
            ip = x_forwarded_for.split(',')[0].strip()
        else:
            ip = request.META.get('REMOTE_ADDR')
        return ip
    
    def _validate_ip_access(self, app_client: AppClient, request: HttpRequest) -> bool:
        """Validate IP access permissions"""
        allowed_ips = app_client.get_allowed_ips_list()
        if not allowed_ips:  # If no IP restrictions are set, allow all IPs
            return True
        
        client_ip = self._get_client_ip(request)
        
        for allowed_ip in allowed_ips:
            try:
                # Support single IPs and CIDR ranges
                if '/' in allowed_ip:
                    network = ipaddress.ip_network(allowed_ip, strict=False)
                    if ipaddress.ip_address(client_ip) in network:
                        return True
                else:
                    if client_ip == allowed_ip:
                        return True
            except (ipaddress.AddressValueError, ValueError):
                # Ignore invalid IP formats
                continue
        
        return False
    
    def _authenticate_app_client(self, request: HttpRequest, key: str) -> Optional[AppClient]:
        """General logic for authenticating an AppClient"""
        try:
            app_client = AppClient.objects.get(secret_key=key)
            
            # Check if the application is active
            if not app_client.is_active():
                return None
            
            # Validate IP access permissions
            if not self._validate_ip_access(app_client, request):
                return None
            
            # Add permission scopes to the request for subsequent permission checks
            request.app_client_scopes = app_client.get_scopes_list()
            
            return app_client
            
        except AppClient.DoesNotExist:
            return None


class AppClientApiKey(AppClientAuthMixin, APIKeyQuery):
    """AppClient API Key authentication based on query parameters"""
    param_name = "api_key"

    def authenticate(self, request: HttpRequest, key: str) -> Optional[AppClient]:
        return self._authenticate_app_client(request, key)


class AppClientApiKeyHeader(AppClientAuthMixin, APIKeyHeader):
    """AppClient API Key authentication based on request headers"""
    param_name = "X-API-Key"

    def authenticate(self, request: HttpRequest, key: str) -> Optional[AppClient]:
        return self._authenticate_app_client(request, key)


class AppClientBearer(AppClientAuthMixin, HttpBearer):
    """AppClient authentication based on Bearer Token"""

    def authenticate(self, request: HttpRequest, token: str) -> Optional[AppClient]:
        return self._authenticate_app_client(request, token)

Create authentication instances, available for direct use in the API

api_key_auth = AppClientApiKey() api_key_header_auth = AppClientApiKeyHeader() api_key_bearer_auth = AppClientBearer()

## Minimal Implementation

This complete authentication logic may seem overly complex to you

No worries, I've also created a minimal version. It removes permissions, IP restrictions and other extraneous elements, only validating whether the api-key is valid.

The implementation code is in `src/core/authentication/api_key.py`

The custom `ApiKey` class inherits from `ninja.security.APIKeyQuery` class, indicating that the `api_key` parameter needs to be included in the **GET query params**.

```python
from ninja.security import APIKeyQuery
from apps.app.models import AppClient


class ApiKey(APIKeyQuery):
    param_name = "api_key"

    def authenticate(self, request, key):
        try:
            c = AppClient.objects.get(secret_key=key)
            print(f'api key authenticated: {c.secret_key}, app id: {c.app_id}')
            return c
        except AppClient.DoesNotExist:
            pass

Usage is also straightforward:

from apps.app.models import AppClient
from core.authentication.api_key import ApiKey

@router.get("/data", auth=ApiKey())
def get_data(request):
   assert isinstance(request.auth, AppClient)
   return {"data": "success"}

When making client requests, include the secret_key of the AppClient in the ==GET query params==.

If authentication succeeds, ninja will automatically add the AppClient instance to request.auth, allowing you to directly access this instance in the endpoint code.

Summary

This article actually ended up over 20,000 words long 🕶

And this is after removing a lot of code...

One more time for those interested: you can reply with API Authentication in the back end of our public account to get the complete code.

Such a long article, probably no one will have the patience to read all the way to the end, right~

Peace out, peace out~

_EOF_

Implementing an application authentication mechanism similar to Tencent or Alibaba Cloud in django-ninja.

本文作者:
程序设计实验室
版权声明:
本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!

相关文章

Django中间件之实现Admin后台IP白名单

前言 Django自带的Admin管理后台很方便,但是实际生产环境真的会有挺多安全问题的,在admin的安全防护这方面,我之前就研究实现了给admin加上登录验证码和限流功能,可以参考这篇文章:给Django Admin添加验证码和多次登录尝试限制 不过就在内部使用的话就好很多,因此我们可以通过Django的中间件来实现IP过滤,所以本文中涉及到两个知识点,一个是Django的中间件,另外一个是P

2022-08-26 · Django · 约 1 分钟

Django统一包装接口返回数据格式

前言 最近实在太忙了,开始了一个新的项目,为了快速形成产品,我选择了Django来实现后端,然后又拿起了之前我封装了项目脚手架「DjangoStarter」。 由于前段时间我写了不少.NetCore的后端代码,对CRUD的接口开发又有了一些新的理解,所以肯定也要把DjangoStarter改造一番,改得更加顺手~ 题外话:话说我在前端终于真正用上react,不得不说 ts + react 真好用,

2022-08-26 · Django · 约 1 分钟

告别单调,Django后台主页改造

前言 之前我做了个Django的项目,为了让管理后台更加美观,我对Django(应该说是SimpleUI的)默认的Admin后台主页进行改造,具体可以看这篇文章:项目完成 - 基于Django3.x版本 - 开发部署小结 之前的两篇关于Django3.x开发部署小结的文章介绍的都是数据库、接口、性能方面的,看到有朋友留言对后台主页的改造表示有兴趣,所以就写一篇来介绍一下~ 分析 改造基于我定制的

2022-06-28 · Django · 约 1 分钟

讨论

共 0 条

良言一句三冬暖,恶语伤人六月寒。

加载评论中…

Implementing an application authentication mechanism similar to Tencent or Alibaba Cloud in django-ninja. · StarBlog