vue 参数加密_Vue+DRF实战10.JWT用户认证原理、用户注册功能实现

本文介绍了JWT在Django REST framework中的应用,包括全局认证的配置、部分接口权限校验、JWT原理、自定义用户认证、序列化器字段验证、用户密码加密、注册后返回token等内容,适合前端后端分离项目的开发者参考。
摘要由CSDN通过智能技术生成

内容提要:

  • JWT用户认证原理和用户注册功能实现。

《Python前后端分离开发Vue+Django REST framework实战》作者bobby

——学习来源

52c9a2a00a97c4f3f66f16eed87fa34d.png

7.2只对部分接口做权限校验

在settings.py中配置了全局的认证后(REST_FRAMEWORK的DEFAULT_AUTHENTICATION_CLASSES),每个接口都会使用全局认证配置。

如果想只针对部分接口进行权限校验,则应该取消全局配置,在需要认证的接口中增加如下设置。


from rest_framework.authentication import TokenAuthentication

class GoodsListViewset(mixins.ListModelMixin,viewsets.GenericViewSet):
...省略...
authentication_classes = (TokenAuthentication,)
...省略...
7.3JWT用户认证原理
  • 参考资料:https://www.cnblogs.com/wenqiangit/p/9592132.html

drf的token认证模式有缺点(例如token无过期),通常前后端分离项目采用JWT(Json Web Token)用户认证模式。

传统验证用户登录信息的方式,是由后端根据用户信息生成一个token,保存token和用户id存入数据库或session,把token传给用户,存入浏览器cookie,之后请求带上cookie,后端根据cookie来判断用户权限。缺点如下。

  • js可以操作cookie。

  • 可以被xsrf。

  • 验证信息如果存在数据库中,每次认证查询会加大开销。

Json Web Token(JWT)是一个开放标准(RFC 7519),它定义了一种用于简洁,自包含的用于通信双方之间以JSON对象的形式安全传递信息的方法。JWT可以使用HMAC算法或者是RSA的公钥秘钥进行签名。有以下优点。

  • 简洁。

  • 通过URL,POST参数或者HTTP header发送。

  • 自包含。

  • 负载包含了所有用户所需信息,避免了多次查询数据库。

JWT组成 

da92077def8c32e1753bceaf7accb665.png

  • Header头部:包含token类型和采用的加密算法。

  • Payload负载:存放用户的信息,例如iss(签发者)、exp(过期时间)等。

  • Signature签名:根据header的算法,对payload进行签名,生成后的秘钥串会和header和payload放在一起。(服务器判断时会对秘钥串进行解密或者对前面的base进行加密比对,如果不同则表示token是伪造的)

JWT是通过算法进行用户信息的获取,不需要存储到服务器。

JWT用户认证流程 

52e18047d3d45273286ccd190475cdd2.png

7.4django-rest-framework使用JWT

使用github上第三方库djangorestframework-jwt实现DRF的JWT。

  1. 安装djangorestframework-jwt。

    pip install djagnorestframework-jwt -i https://pypi.douban.com/simple
  2. 在setting.py中增加jwt配置。

    REST_FRAMEWORK = {
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
    'PAGE_SIZE': 2,
    'DEFAULT_AUTHENTICATION_CLASSES': [
    'rest_framework_jwt.authentication.JSONWebTokenAuthentication', # 取出用户信息放入request.user
    ],
    }
  3. 增加urls配置。

    from rest_framework_jwt.views import obtain_jwt_token
    urlpatterns = [
    url(r'^jwt-auth/', obtain_jwt_token),
    ]
  4. 获取toknen后,放入header即可。

    {'Authorization': 'JWT eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJ1
    c2VybmFtZSI6ImFkbWluIiwiZXhwIjoxNTk2OTQ2MDA1LCJlbWFpbCI6ImFkbWluQGFkbWluLmNvbSJ9
    .uQzIc4Bonar5faWQThKmSTMcDnitiIW0uWh6ziB6tLg'}

其他的常用配置如下(github对应的文档打不开了):

# settings.py
import datetime
JWT_AUTH = {
'JWT_EXPIRATION_DELTA':datetime.timedelta(seconds=300), # token有效期
# 'JWT_EXPIRATION_DELTA':datetime.timedelta(days=7),
'JWT_AUTH_HEADER_PREFIX': 'JWT', # 设置header请求时Authorization值的前缀
}
7.5自定义用户认证函数

默认的用户认证(authenticate)使用的是用户名密码验证,如果想自定义方式验证(例如输入的username可以匹配手机号),可以自定义编写一个继承ModelBackend,重写authenticate方法的类。

# users的views.py
from django.contrib.auth.backends import ModelBackend
from django.contrib.auth import get_user_model
from django.db.models import Q

User = get_user_model()
class CustomBackend(ModelBackend):
"""自定义用户认证"""
def authenticate(self, request, username=None, password=None, **kwargs):
try:
user = User.objects.get(Q(username=username)|Q(mobile=username))
if user.check_password(password):
return user
except Exception as e:
return None

在settings.py中配置authentication_backends属性,指定自定义类为认证时调用的类。

# settings.py
AUTHENTICATION_BACKENDS = (
'users.views.CustomBackend',
)
7.6通过serializers单独验证models某个字段

在之前的serializers中,直接使用了serializers.ModelSerializer,但是验证码模块不适合这样使用。因为验证码模块的mobile和code都是必填,如果用ModelSerializer则会校验这两项(但我们只需求他mobile是必填),所以我们可以改为使用serializers.Serializer。

在serializers.Serializer里,如果想针对某个字段做自定义的校验,可以重写validate_开头的函数。

import re
from datetime import datetime,timedelta
from rest_framework import serializers
from django.contrib.auth import get_user_model
from DRFDemo.settings import REGEX_MOBILE
from .models import VerifyCode

User = get_user_model()

class GoodsCategorySerializer3(serializers.Serializer):
mobile = serializers.CharField(max_length=11)

def validate_mobile(self,mobile):
if User.objects.fileter(mobile = mobile).count():
raise serializers.ValidationError("用户已经存在")

# settings.py配置的属性REGEX_MOBILE = "^1[358]\d{9}$|^147\d{8}$|^176\d{8}$"
if not re.match(REGEX_MOBILE,mobile):
raise serializers.ValidationError("手机号码非法")

# 验证码发送频率
one_mintes_ago = datetime.now() - timedelta(hours=0,minutes=1,seconds=0) # 前一分钟的时间
if VerifyCode.objects.filter(add_time__gt=one_mintes_ago,mobile=mobile): # 搜索该手机号是否有1分钟内发送验证码的记录
raise serializers.ValidationError("发送验证码间隔小于60秒")

return mobile

在views.py中的serializer验证和froms验证的方式是一样的。

from .serializers import VerifyCode
from .serializers import SmsSerializer
from rest_framework.mixins import CreateModelMixin
from rest_framework import viewsets
from rest_framework.response import Response
from rest_framework import status

class SmsCodeViewset(CreateModelMixin,viewsets.GenericViewSet):
"""发送短信验证码"""
serializer_class = SmsSerializer
# 重写create
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True) # 如果执行抛异常则不往后执行,返回400状态码
mobile = serializer.validated_data["mobile"]

# 此处就可以编写创建逻辑代码,此处省略。下面为简略实现验证通过后的新增
code_record = VerifyCode(code=1234,mobile=mobile)
code_record.save()
return Response({
"status":0,
"mobile":mobile
},status=status.HTTP_201_CREATED)
7.7serializers字段验证
  • 除了自定义的validate_xxx之外,DRF还提供了validation class,用于提供便捷的字段的验证。

  • 官方文档:https://www.django-rest-framework.org/api-guide/validators/

  • 例如:字段是唯一,如果数据库已经存在则返回错误。

    # serializers.py
    from rest_framework.validator import UniqueValidator
    username = serializers.CharField(required=True,allow_blank=False,validators=[UniqueValidator(queryset=User.objects.all(),message="用户已经存在")])
    # message是错误时返回的提示内容
7.8用户密码加密保存

用户注册时,如果没有做特殊处理,保存的密码也是明文的,这样会导致安全问题。如果想设置把密码加密后保存,可以采用以下两种方法。

方法一:重写create方法

重写create方法,将password加密后在存储。

# serializers.py
from rest_framework import serializers

class UserRegSerializer(serializers.ModelSerializer):
def create(self,validated_data):
user = super(UserRegSerializer, self).create(validated_data=validated_date)
user.set_password(validated_date["password"])
user.save()
return user

方法二:使用信号量。

信号量是指django进行某些操作后(例如model),会发送一个全局信号,该信号可以捕捉,捕捉后加入自己的逻辑处理。

官方文档:https://docs.djangoproject.com/en/2.1/ref/signals/#django.db.models.signals.post_save

用户保存后会发送post_save信号,通过捕获该信号来控制密码保存操作。

  1. 新建signals.py文件。

    from django.db.models.signals import post_save
    from django.dispatch import receiver
    from django.contrib.auth import get_user_model
    User = get_user_model()

    @receiver(post_save,sender=User) # 指定接收信号,从哪个model传递过来的。
    def create_auth_password(sender,instance=None,created=False,**kwargs):
    # instance代表models的对象,create代表该信号是否为新建操作
    if created:
    password = instance.password
    instance.set_password(password)
    instance.save()
  2. 在apps.py中重写ready函数。

    from django.apps import AppConfig

    class UserConfig(AppConfig):
    name = 'users'

    def ready(self):
    import users.signals
7.9注册后直接返回token

注册后可以让用户输入用户名密码自行登录,也可以直接当做用户已登录(返回首页)。如果需要注册成功后默认是登录状态,则需要在注册成功的接口中返回token(JWT模式)。

from rest_framework import viewsets
from rest_framework.mixins import CreateModelMixin
from rest_framework_jwt.serializers import jwt_payload_handler, jwt_encode_handler
from rest_framework.response import Response
from rest_framework import status
from django.contrib.auth import get_user_model

from .serializers import UserRegSerializer

User = get_user_model()
class UserViewset(CreateModelMixin,viewsets.GenericViewSet):
"""用户注册"""
serializer_class = UserRegSerializer
queryset = User.objects.all()

def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)

# 修改下面的内容
user = self.perform_create(serializer)
# 以下代码需要自行断点和查阅源码后编写,找到jwt如何生成token
re_dict = serializer.data
payload = jwt_payload_handler(user)
re_dict["token"] = jwt_encode_handler(payload)

headers = self.get_success_headers(serializer.data)
# 返回时使用更新后的数据re_dict
return Response(re_dict, status=status.HTTP_201_CREATED, headers=headers)

def perform_create(self, serializer):
return serializer.save() # 需要返回user对象
7.10serializers的常用知识
  • self.initial_data:存储了前端传递过来的参数,例如self.initial_data["username"]获取前端传参username。

  • def validate(self,attrs):作用域所有的serializers之上,attrs是所有字段validate_xx处理之后,dict类型的所有参数。用于对所有参数做最后的处理。

    def validate(self,attrs):
    # 将username赋值给mobile
    attrs["mobile"] = attrs["username"]
    # 删除多余字段code
    del attrs["code"]
    return attrs
  • help_text:文档的提示内容。

    code = serializers.CharField(required=True,max_length=4,min_length=4,help_text="验证码")
  • error_messages:对应字段错误时的提示信息。

    code = serializers.CharField(required=True,max_length=4,min_length=4,error_messages={"blank":"请输入验证码",required":"请输入验证码","max_length":"验证码格式错误","min_length":"验证码格式错误"},help_text="验证码")
  • write_only:返回前端时不序列化该字段。

    code = serializers.CharField(required=True,write_only=True,max_length=4,min_length=4,error_messages={"blank":"请输入验证码",required":"请输入验证码","max_length":"验证码格式错误","min_length":"验证码格式错误"},help_text="验证码")
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值