今天开始学用户登录和手机注册了, 不过说实在的, 我听得很懵逼, 尤其用户论证的原理, 不管了, 先记下来怎么用吧,
我们参考drf官方手册和操作的博客, 官网文档地址: https://www.django-rest-framework.org/api-guide/authentication/
参考博客: https://www.cnblogs.com/derek1184405959/p/8813641.html
用户论证的方式有 session和token, 在前后端分离中一般都token认证, 至于token的代码分析就不说了, 在这主要说说drf token认证的主要流程: Token 是在服务端产生的。如果前端使用用户名/密码向服务端请求认证,服务端认证成功,那么在服务端会返回 Token 给前端。前端可以在每次请求的时候带上 Token 证明自己的合法地位.
详细教程: https://www.cnblogs.com/haowen980/p/9416531.html
(1)INSTALL_APP中添加
INSTALLED_APPS = (
...
'rest_framework.authtoken'
)
token会生成一张表authtoken_token,所以要运行migrations和migrate, 一般在installed_apps下注册的apps都会生成表, 所以要运行生成表的命令.
(2)url配置
from rest_framework.authtoken import views
urlpatterns = [
# token
path('api-token-auth/', views.obtain_auth_token)
]
在官方文档说明了, 我们向这个url中post相应的username = xxx password=xxx , 倘若认证成功, 服务器会给我们返回一个token值, 以后再请求任意需要认证的视图时, 在请求的头部带上这个token值, 就能成功返回你相应的数据, 我们为了或得token值, 用软件向该url发送相应的username和password, 我用postman
(3)postman发送数据
注意这里有一个细节:
在测试的时候, 你要post的那个用户不能在系统中登录, 要是系统登录了而且还向服务器post改用户, 服务器会报csrf认证错误
{"detail":"CSRF Failed: CSRF token missing or incorrect."}, 这个错误找了我好久, 还是不经意间试出来的
(4)客户端身份验证
对于客户端进行身份验证,令牌密钥应包含在 Authorization
HTTP header 中。关键字应以字符串文字 “Token” 为前缀,用空格分隔两个字符串。例如:
Authorization: Token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b
注意: 如果您想在 header 中使用不同的关键字(例如 Bearer
),只需子类化 TokenAuthentication
并设置 keyword
类变量。
如果成功通过身份验证,TokenAuthentication
将提供以下凭据。
request.user
是一个 Django User
实例.request.auth
是一个 rest_framework.authtoken.models.Token
实例.未经身份验证的响应被拒绝将导致 HTTP 401 Unauthorized
的响应和相应的 WWW-Authenticate header。例如:
WWW-Authenticate: Token
要想获取request.user和request.auth还要在settings中添加
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.BasicAuthentication',
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.TokenAuthentication'
)
}
drf的token缺点
jwt 讲解: https://www.cnblogs.com/cjsblog/p/9277677.html
jwt可是帮我们解决了好多问题, 详情见上面的推文
django-rest-framework-jwt 使用方法:http://getblimp.github.io/django-rest-framework-jwt/
(1)安装
pip install djangorestframework-jwt
(2)使用
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.BasicAuthentication',
'rest_framework.authentication.SessionAuthentication',
'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
)
}
(3)url
url(r'^jwt-auth/', obtain_jwt_token),
(4)postman
post形式:http://127.0.0.1:8000/jwt-auth/
Now in order to access protected api urls you must include the Authorization: JWT
header.
$ curl -H "Authorization: JWT " http://localhost:8000/protected-url/
vue中登录接口是login
//登录
export const login = params => {
return axios.post(`${local_host}/login/`, params)
}
后台的接口跟前端要一致
urlpatterns = [
# jwt的认证接口
path('login/', obtain_jwt_token )
]
现在就可以登录了
jwt接口它默认采用的是用户名和密码登录验证,如果用手机登录的话,就会验证失败,所以我们需要自定义一个用户验证
自定义用户认证
(1)settings中配置
AUTHENTICATION_BACKENDS = (
'users.views.CustomBackend',
)
(2)users/views.py
# 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, 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
(3)JWT有效时间设置
settings中配置
import datetime
#有效期限
JWT_AUTH = {
'JWT_EXPIRATION_DELTA': datetime.timedelta(days=7), #也可以设置seconds=20
'JWT_AUTH_HEADER_PREFIX': 'JWT', #JWT跟前端保持一致,比如“token”这里设置成JWT
}
(1)注册
“开发认证”-->>“签名管理”-->>“模板管理”
还要添加iP白名单,测试就用本地ip,部署的时候一定要换成服务器的ip
(2)发送验证码
apps下新建utils文件夹。再新建yunpian.py,代码如下:
# apps/utils/yunpian.py
import requests
import json
class YunPian(object):
def __init__(self, api_key):
self.api_key = api_key
self.single_send_url = "https://sms.yunpian.com/v2/sms/single_send.json"
def send_sms(self, code, mobile):
#需要传递的参数
parmas = {
"apikey": self.api_key,
"mobile": mobile,
"text": "【慕雪生鲜超市】您的验证码是{code}。如非本人操作,请忽略本短信".format(code=code)
}
response = requests.post(self.single_send_url, data=parmas)
re_dict = json.loads(response.text)
return re_dict
if __name__ == "__main__":
#例如:9b11127a9701975c734b8aee81ee3526
yun_pian = YunPian("2e87d1xxxxxx7d4bxxxx1608f7c6da23exxxxx2")
yun_pian.send_sms("2018", "手机号码")
手机号验证:
(1)settings.py
# 手机号码正则表达式
REGEX_MOBILE = "^1[358]\d{9}$|^147\d{8}$|^176\d{8}$"
(2)users下新建serializers.py,代码如下:
# users/serializers.py
import re
from datetime import datetime, timedelta
from MxShop.settings import REGEX_MOBILE
from users.models import VerifyCode
from rest_framework import serializers
from django.contrib.auth import get_user_model
User = get_user_model()
class SmsSerializer(serializers.Serializer):
mobile = serializers.CharField(max_length=11)
#函数名必须:validate + 验证字段名
def validate_mobile(self, mobile):
"""
手机号码验证
"""
# 是否已经注册
if User.objects.filter(mobile=mobile).count():
raise serializers.ValidationError("用户已经存在")
# 是否合法
if not re.match(REGEX_MOBILE, mobile):
raise serializers.ValidationError("手机号码非法")
# 验证码发送频率
#60s内只能发送一次
one_mintes_ago = datetime.now() - timedelta(hours=0, minutes=1, seconds=0)
if VerifyCode.objects.filter(add_time__gt=one_mintes_ago, mobile=mobile).count():
raise serializers.ValidationError("距离上一次发送未超过60s")
return mobile
(3)APIKEY加到settings里面
#云片网APIKEY
APIKEY = "xxxxx327d4be01608xxxxxxxxxx"
(4)views后台逻辑
我们要重写CreateModelMixin的create方法,下面是源码:
class CreateModelMixin(object):
"""
Create a model instance.
"""
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
def perform_create(self, serializer):
serializer.save()
def get_success_headers(self, data):
try:
return {'Location': str(data[api_settings.URL_FIELD_NAME])}
except (TypeError, KeyError):
return {}
需要加上自己的逻辑
users/views.py
from rest_framework.mixins import CreateModelMixin
from rest_framework import viewsets
from .serializers import SmsSerializer
from rest_framework.response import Response
from rest_framework import status
from utils.yunpian import YunPian
from MxShop.settings import APIKEY
from random import choice
from .models import VerifyCode
class SmsCodeViewset(CreateModelMixin,viewsets.GenericViewSet):
'''
手机验证码
'''
serializer_class = SmsSerializer
def generate_code(self):
"""
生成四位数字的验证码
"""
seeds = "1234567890"
random_str = []
for i in range(4):
random_str.append(choice(seeds))
return "".join(random_str)
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
#验证合法
serializer.is_valid(raise_exception=True)
mobile = serializer.validated_data["mobile"]
yun_pian = YunPian(APIKEY)
#生成验证码
code = self.generate_code()
sms_status = yun_pian.send_sms(code=code, mobile=mobile)
if sms_status["code"] != 0:
return Response({
"mobile": sms_status["msg"]
}, status=status.HTTP_400_BAD_REQUEST)
else:
code_record = VerifyCode(code=code, mobile=mobile)
code_record.save()
return Response({
"mobile": mobile
}, status=status.HTTP_201_CREATED)
云片网单条短信发送的使用说明:
(5)配置url
from users.views import SmsCodeViewset
# 配置codes的url
router.register(r'code', SmsCodeViewset, base_name="code")
开始验证
输入不合法的手机号
输入合法的手机号