基于Django REST API实现员工邮箱+密码JWT登录功能
要实现JWT登录,核心是搞定密码的哈希存储和验证逻辑,Django自带的密码工具已经很成熟,给你两种可行方案,按需选择:
方案1:直接扩展Employee模型(适合不需要Django内置用户权限的场景)
1. 修正并更新Employee模型
给Employee添加密码字段,同时处理密码哈希避免明文存储,还要修正原代码里的语法错误(比如字段类首字母需大写、必填参数不能缺):
from django.db import models from django.contrib.auth.hashers import make_password, check_password class Employee(models.Model): Fname = models.CharField(max_length=50) Lname = models.CharField(max_length=50) Personal_email = models.EmailField(unique=True) # 设为唯一,防止重复注册 Department = models.CharField(choices=[('HR', '人力资源'), ('DEV', '开发部')], max_length=10) # 补全ChoiceField的选项参数 Designation = models.CharField(max_length=50) password = models.CharField(max_length=128) # 存储哈希后的密码,128位长度足够 def save(self, *args, **kwargs): # 保存时自动哈希密码,已哈希过的内容不再重复处理 if self.password and not self.password.startswith('pbkdf2_sha256$'): self.password = make_password(self.password) super().save(*args, **kwargs) def check_password(self, raw_password): # 自定义密码验证方法 return check_password(raw_password, self.password)
注意:原代码里的
charfield()、Emailfield都是小写错误,Django模型字段类首字母必须大写,比如CharField、EmailField,别忘了修正。
2. 调整序列化器
注册时让密码仅作为输入字段,返回结果中不暴露:
from rest_framework import serializers from .models import Employee class EmployeeCreateSerializer(serializers.ModelSerializer): password = serializers.CharField(write_only=True) # 只写字段,序列化返回时自动排除 class Meta: model = Employee fields = ['Fname', 'Lname', 'Personal_email', 'Department', 'Designation', 'password'] class EmployeeSerializer(serializers.ModelSerializer): class Meta: model = Employee exclude = ['password'] # 显式排除密码字段
原代码里
Modelserializer少了大写的M,fields = '__all__缺了闭合引号,这些语法错误必须修正,否则无法运行。
3. 实现JWT登录(用主流的simplejwt库)
先安装依赖:
pip install djangorestframework-simplejwt
然后在settings.py中配置:
INSTALLED_APPS = [ # ...你的其他应用 'rest_framework', 'rest_framework_simplejwt', ] REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': ( 'rest_framework_simplejwt.authentication.JWTAuthentication', ), } # 可选:配置令牌有效期 from datetime import timedelta SIMPLE_JWT = { 'ACCESS_TOKEN_LIFETIME': timedelta(minutes=60), 'REFRESH_TOKEN_LIFETIME': timedelta(days=1), }
接下来写自定义登录视图,验证邮箱和密码:
from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import status from rest_framework_simplejwt.tokens import RefreshToken from .models import Employee from .serializers import EmployeeSerializer class EmployeeLoginView(APIView): def post(self, request): email = request.data.get('Personal_email') password = request.data.get('password') if not email or not password: return Response({'error': '请填全邮箱和密码'}, status=status.HTTP_400_BAD_REQUEST) try: employee = Employee.objects.get(Personal_email=email) except Employee.DoesNotExist: return Response({'error': '该邮箱未注册'}, status=status.HTTP_401_UNAUTHORIZED) if employee.check_password(password): # 生成JWT令牌 refresh = RefreshToken.for_user(employee) return Response({ 'refresh': str(refresh), 'access': str(refresh.access_token), 'employee': EmployeeSerializer(employee).data }) else: return Response({'error': '密码错误'}, status=status.HTTP_401_UNAUTHORIZED)
要是想更规范,让Employee继承Django的
AbstractBaseUser和PermissionsMixin,这样就能直接用simplejwt的默认视图,不用自己写登录逻辑。修改后的模型如下:
from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin, BaseUserManager class EmployeeManager(BaseUserManager): def create_user(self, personal_email, password=None, **extra_fields): if not personal_email: raise ValueError('必须提供个人邮箱') email = self.normalize_email(personal_email) user = self.model(Personal_email=email, **extra_fields) user.set_password(password) user.save(using=self._db) return user class Employee(AbstractBaseUser, PermissionsMixin): Fname = models.CharField(max_length=50) Lname = models.CharField(max_length=50) Personal_email = models.EmailField(unique=True) Department = models.CharField(choices=[('HR', '人力资源'), ('DEV', '开发部')], max_length=10) Designation = models.CharField(max_length=50) is_active = models.BooleanField(default=True) is_staff = models.BooleanField(default=False) objects = EmployeeManager() USERNAME_FIELD = 'Personal_email' # 指定用邮箱作为登录标识 REQUIRED_FIELDS = ['Fname', 'Lname'] def __str__(self): return self.Personal_email
这种情况下,直接用simplejwt的默认路由即可,无需自定义登录视图:
from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView urlpatterns = [ # ...其他路由 path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'), path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'), ]
登录时向/api/token/发送POST请求,传递username(即Personal_email)和password参数即可。
方案2:关联Django内置User模型(适合需要用到权限、组等Django用户功能的场景)
1. 修改模型,添加一对一关联
from django.db import models from django.contrib.auth.models import User class Employee(models.Model): user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='employee') Fname = models.CharField(max_length=50) Lname = models.CharField(max_length=50) Personal_email = models.EmailField(unique=True) Department = models.CharField(choices=[('HR', '人力资源'), ('DEV', '开发部')], max_length=10) Designation = models.CharField(max_length=50)
2. 更新注册视图,同时创建User和Employee
注册时先创建User对象,再绑定对应的Employee信息:
from rest_framework import generics from rest_framework.response import Response from django.contrib.auth.models import User from .models import Employee from .serializers import EmployeeCreateSerializer class RegisterEmployeeView(generics.CreateAPIView): serializer_class = EmployeeCreateSerializer def post(self, request): # 先创建User,用邮箱作为用户名 user = User.objects.create_user( username=request.data['Personal_email'], email=request.data['Personal_email'], password=request.data['password'] ) # 再创建Employee,关联刚创建的User employee_data = { 'user': user.id, 'Fname': request.data['Fname'], 'Lname': request.data['Lname'], 'Personal_email': request.data['Personal_email'], 'Department': request.data['Department'], 'Designation': request.data['Designation'] } serializer = self.get_serializer(data=employee_data) serializer.is_valid(raise_exception=True) serializer.save() return Response({ "employee": serializer.data, "message": "员工注册成功..!", })
3. JWT登录配置
同样使用simplejwt的默认视图,登录时传递username(即Personal_email)和password即可。后续需要获取员工信息时,直接通过request.user.employee就能拿到关联的Employee对象。
路由配置示例
from django.urls import path from .views import RegisterEmployeeView, EmployeeLoginView from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView urlpatterns = [ path('register/', RegisterEmployeeView.as_view(), name='register-employee'), # 方案1自定义登录视图用这条 path('login/', EmployeeLoginView.as_view(), name='login-employee'), # 方案2或方案1继承AbstractBaseUser时用以下路由 path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'), path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'), ]
内容的提问来源于stack exchange,提问作者orange freeze

