一 认证简介
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',
}
- JWT_EXPIRATION_DELTA 指明token的有效期
# -*- 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<mobile>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, ]