You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Django + simplejwt实现多次登录尝试失败后账户锁定功能的咨询

现成可用的Python包

目前有直接适配simplejwt的第三方包可以满足需求,不需要自己从零开发:

  • djangorestframework-simplejwt-ratelimit:专门为simplejwt登录场景开发的限流锁定插件,原生支持按用户名/IP维度计数错误登录次数,可自定义最大尝试次数、锁定时长、错误返回提示,仅需少量配置即可接入,不需要大幅改动现有simplejwt的业务逻辑。
  • 也可以选择通用的DRF限流扩展django-rest-framework-throttling的定制版本,不过适配成本比上面的专用包略高。
自研实现方案(无额外依赖)

如果不想引入第三方包,仅基于simplejwt本身也可以快速实现该功能,步骤如下:

步骤1:扩展用户模型或新增失败登录记录表

两种方案二选一即可:

  1. 直接在现有用户模型新增字段,适合仅需要按账号锁定的场景:
from django.db import models
from django.contrib.auth.models import AbstractUser

class CustomUser(AbstractUser):
    failed_login_attempts = models.IntegerField(default=0, verbose_name="失败登录次数")
    lock_until = models.DateTimeField(null=True, blank=True, verbose_name="锁定截止时间")
  1. 单独新建失败登录日志表,适合需要按IP锁定、留存登录审计日志的场景:
from django.db import models
from django.contrib.auth import get_user_model

User = get_user_model()

class FailedLoginLog(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE, null=True, verbose_name="关联用户")
    ip_address = models.GenericIPAddressField(verbose_name="登录IP")
    attempt_time = models.DateTimeField(auto_now_add=True, verbose_name="尝试时间")

步骤2:自定义simplejwt登录视图

重写simplejwt自带的TokenObtainPairView,在密码校验前后加入锁定判断、计数逻辑:

from rest_framework_simplejwt.views import TokenObtainPairView
from rest_framework.response import Response
from rest_framework import status
from datetime import datetime, timedelta
from django.conf import settings
from .models import CustomUser

# 配置项建议统一放到settings.py中
MAX_FAILED_ATTEMPTS = 5  # 最大错误尝试次数
LOCK_DURATION = timedelta(minutes=30)  # 单次锁定时长

class CustomTokenObtainPairView(TokenObtainPairView):
    def post(self, request, *args, **kwargs):
        username = request.data.get("username")
        client_ip = request.META.get("REMOTE_ADDR")

        # 校验用户是否存在
        try:
            user = CustomUser.objects.get(username=username)
        except CustomUser.DoesNotExist:
            # 可按需添加对不存在用户的IP限流逻辑,防止账号枚举
            return Response({"detail": "用户名或密码错误"}, status=status.HTTP_401_UNAUTHORIZED)

        # 判断用户是否处于锁定状态
        if user.lock_until and user.lock_until > datetime.now():
            remaining_minute = (user.lock_until - datetime.now()).seconds // 60
            return Response(
                {"detail": f"账号已锁定,请{remaining_minute}分钟后再尝试"},
                status=status.HTTP_403_FORBIDDEN
            )

        # 执行原有登录校验逻辑
        serializer = self.get_serializer(data=request.data)
        try:
            serializer.is_valid(raise_exception=True)
        except Exception:
            # 校验失败,计数+1,达到阈值则触发锁定
            user.failed_login_attempts += 1
            if user.failed_login_attempts >= MAX_FAILED_ATTEMPTS:
                user.lock_until = datetime.now() + LOCK_DURATION
            user.save()
            return Response({"detail": "用户名或密码错误"}, status=status.HTTP_401_UNAUTHORIZED)

        # 校验成功,重置失败计数和锁定状态
        user.failed_login_attempts = 0
        user.lock_until = None
        user.save()

        return Response(serializer.validated_data, status=status.HTTP_200_OK)

步骤3:替换原有登录路由

把路由配置中原来的simplejwt登录视图替换为自定义的视图即可:

from django.urls import path
from .views import CustomTokenObtainPairView
from rest_framework_simplejwt.views import TokenRefreshView

urlpatterns = [
    path("api/login/", CustomTokenObtainPairView.as_view(), name="token_obtain_pair"),
    path("api/token/refresh/", TokenRefreshView.as_view(), name="token_refresh"),
]

可选优化点

  • 增加IP维度的锁定逻辑,防止同一个IP批量爆破多个账号
  • 实现阶梯锁定规则,比如第一次锁定30分钟,第二次1小时,第三次24小时
  • 后台增加用户锁定管理入口,支持管理员手动解锁账号
  • 对不存在的用户名请求也做IP限流,避免恶意用户枚举系统账号

内容的提问来源于stack exchange,提问作者Pythoner

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.27 12:15:05