一 认证简介
Django 自带一个用户认证系统,这个系统处理用户帐户、组、权限和基于 cookie 的会话.
只有认证通过的用户才能访问指定的url地址,比如:查询课程信息,需要登录之后才能查看,没有登录,就不能查看,这时候需要用到认证组件
Rest-Framework中的认证
在restframework中,认证即是通过继承BaseAuthentication重构认证的类,认证的逻辑在类的authenticate方法中实现,通过判断用户传递的信息,如果认证成功返回(用户,用户Token)元组,会将用户对象封装到request里,通过request.用户可以获得用户的相关信息。
安装
pip install djangorestframework-jwt
局部使用的时候, 哪个类需要加验证,就在哪个类中加上该属性: authentication_classes = [TokenAuth, ]
要注意列表中的类,必须是已经写好的用来验证的类,
列表中有多个类的时候, 如果第一个类就有两个返回值, 那么后面的类将不会再继续进行, 是否进行的关键在于验证成功后返回的值是否为空, 为空则继续循环验证类, 反之则停止
- class Course(APIView):
- authentication_classes = [TokenAuth, ]
-
- def get(self, request):
- return HttpResponse('get')
-
- def post(self, request):
- return HttpResponse('post')
全局使用的时候会导致一个尴尬的问题就是我们写的登录CBV也需要登录认证才行
全局使用的时候,我们实现部分CBV禁用的方式是,在需要的CBV中写上:authentication_classes = [ ]
当我们写了authentication_classes = [ ] 为空的时候,将不会有返回值,就会让我们进行正常登录
- REST_FRAMEWORK = {
- #异常处理
- 'EXCEPTION_HANLER':'mall.utils.exceptions.exception_handler',
- 'DEFAULT_RENDERER_CLASSES': ( # 默认响应渲染类
- 'rest_framework.renderers.JSONRenderer', # json渲染器
- 'rest_framework.renderers.BrowsableAPIRenderer', # 浏览API渲染器
- ),
- 'DEFAULT_AUTHENTICATION_CLASSES': (
- # 这里是全局的 return [auth() for auth in self.authentication_classes]
- # 加上后 所有接口都500
- 'rest_framework_jwt.authentication.JSONWebTokenAuthentication', #token
- 'rest_framework.authentication.BasicAuthentication', # 基本认证
- 'rest_framework.authentication.SessionAuthentication', # session认证
- ),
- }
- JWT_AUTH = {
- 'JWT_EXPIRATION_DELTA': datetime.timedelta(days=2), # 指明token的有效期
- 'JWT_RESPONSE_PAYLOAD_HANDLER': 'users.utils.jwt_response_payload_hanler',
- }
- # -*- coding: utf-8 -*-
- def jwt_response_payload_hanler(token, user=None, request=None):
- """自定义jwt认证返回数据"""
- data = {"id": user.id,"username": user.username,"mobile": user.mobile,"token":token}
- return data
Django REST framework JWT 扩展的说明文档中提供了手动签发JWT的方法
- from rest_framework_jwt.settings import api_settings
-
- jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
- jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
-
- payload = jwt_payload_handler(user)
- token = jwt_encode_handler(payload)
在注册成功后,连同返回token,需要在注册视图中创建token。
修改CreateUserSerializer序列化器,在create方法中增加手动创建token的方法
- # -*- coding: utf-8 -*-
- import re
-
- from rest_framework import serializers
- from users.models import User
-
-
- class CreateUserSerializer(serializers.ModelSerializer):
- password2 = serializers.CharField(label="确认密码",write_only=True)
- sms_code = serializers.CharField(label="短信验证码",write_only=True)
- allow = serializers.CharField(label="同意协议",write_only=True)
- token = serializers.CharField(label="登录状态token",read_only=True)
-
- class Meta:
- model = User
- fields = ("id","username","password","password2","sms_code","mobile","allow","token")
- extra_kwargs = {
- "username":{
- "min_length":5,
- "max_length":20,
- "error_messages":{
- "min_length":"仅允许5-20字符的用户名",
- "max_length":"仅允许5-20字符的用户名",
- }
- },
- "password": {
- "write_only": True,
- "min_length": 8,
- "max_length": 20,
- "error_messages": {
- "min_length": "仅允许8-20个字符的密码",
- "max_length": "仅允许8-20个字符的密码",
- }
- }
- }
-
-
- def validated_mobile(self,value):
- print(value)
- # 校验 手机号
- if not re.match(r'^1[3-9]\d{9}',value):
- raise serializers.ValidationError("手机号码格式错误")
- return value
-
- def validated_allow(self,value):
- # 校验是否同意协议
- if value != "true":
- raise serializers.ValidationError("请您勾选用户协议")
- return value
-
- def validate(self, data):
- # 判断两次密码
- if data['password'] != data['password2']:
- raise serializers.ValidationError("两次输入的密码不一致")
-
- # # 判断短信验证码
- # mobile = data['mobile']
- # redis_conn = get_redis_connection('verify_codes')
- # real_sms_code = redis_conn.get("sms_%s" % mobile) # 二进制
- # if real_sms_code is None:
- # raise serializers.ValidationError("无效的短信验证码")
- # if data['sms_code'] != real_sms_code.decode():
- # raise serializers.ValidationError("短信验证码错误")
- return data
-
- def create(self, validated_data):
- # 创建新用户
- # 移除数据库用户表里没有的字段
- del validated_data['password2']
- del validated_data['sms_code']
- del validated_data['allow']
-
- # user = User.objects.create(**validated_data)
- user = super().create(validated_data)
- user.set_password(validated_data['password']) #继承的一个加密密码的方法
- user.save()
-
- # 加上 token
- from rest_framework_jwt.settings import api_settings
- payload_handler = api_settings.JWT_PAYLOAD_HANDLER
- encode_handler = api_settings.JWT_ENCODE_HANDLER
-
- payload = payload_handler(user)
- token = encode_handler(payload)
- user.token = token
-
- return user
-
-
注意上面是全局
认证组件全局使用
全局使用的时候会导致一个尴尬的问题就是我们写的登录CBV也需要登录认证才行
全局使用的时候,我们实现部分CBV禁用的方式是,在需要的CBV中写上:authentication_classes = [ ]
当我们写了authentication_classes = [ ] 为空的时候,将不会有返回值,就会让我们进行正常登录
注册/验证手机号不需要token
- import random
-
- from django.http import HttpResponse
- from rest_framework.views import APIView
- from rest_framework.generics import GenericAPIView
- from verifications.serializers import ImageCodeCheckSerializer
- from rest_framework.response import Response
- # Create your views here.
-
- from libs.captcha.captcha import captcha
- from libs.aliyunsms.sms_send import send_sms_code
-
- '''
- 图片验证
- 1.第三方插件 captcha
- '''
- class ImageCodeView(APIView):
- authentication_classes = []
- # 图片验证码
- def get(self,request,image_code_id):
- # 获取图片的验证码
- # 1.调用图片模块 接收图片验证码和图片对象
- text,image = captcha.generate_captcha()
- print(text)
- # 2.保存这个图片验证码的真实值 redis
- #redis_conn = get_redis_connection('verify_codes')
- # redis_conn.setex("img_%s" % image_code_id, constants.IMAGE_CODE_REDIS_EXPIRES, text)
- # 保存到session
- request.session['info'] = text
- # 获取 request.session.info
- print(request.session.get("info"))
- return HttpResponse(image,content_type="image/png")
-
-
- # GET /sms_codes/(P
1[3-9]\d{9})/?text=1234&image_code_id=uuid - class SMSCodeView(GenericAPIView):
- authentication_classes = []
- serializer_class = ImageCodeCheckSerializer
- # 发送短信验证码
- def get(self,request,mobile):
- # 1.校验参数 由序列化器完成
- serializer = self.get_serializer(data=request.query_params)
- serializer.is_valid(raise_exception=True)
- # 2.发送短信需生成一个随机的验证码
- sms_code = "%06d" % random.randint(0,999999)
- print("短信验证码: %s" % sms_code)
- # 3.保存短信验证码
- request.session['sms_code'] = sms_code
- # 4.发送短信
- send_sms_code(mobile,sms_code)
- return Response({"message":"ok"})
- # 给这个发送短信的接口做限流 一天最或发5次。
-
-
总结:局部使用,只需要在视图类里加入:
authentication_classes = [TokenAuth, ]