在INSTALLED_APPS中注册:
1 INSTALLED_APPS = (
2 'rest_framework.authtoken'
3 )
然后迁移数据库,会生成一张表authtoken_token,存放用户的token信息:
配置token的url:
1 from rest_framework.authtoken import views
2
3
4 urlpatterns = [
5 path('api-token-auth/', views.obtain_auth_token), # drf-token
6 ]
然后现在测试发起post请求登录,我们使用postman工具来发起请求:
drf返回的token值会保存到数据库中并与用户进行关联:
然后客户端需要进行身份验证,令牌密钥包含在 Authorization
HTTP header 中。关键字应以字符串文字 “Token” 为前缀,用空格分隔两个字符串。例如:
Authorization: Token 30fc1a3cab2d97a6ab3431d603a0bfc40145785b
通过验证TokenAuthentication
将提供以下凭据:
要想获取这两个实例,还要在settings.py中添加以下设置:
1 REST_FRAMEWORK = {
2 'DEFAULT_AUTHENTICATION_CLASSES': (
3 'rest_framework.authentication.BasicAuthentication',
4 'rest_framework.authentication.SessionAuthentication',
5 'rest_framework.authentication.TokenAuthentication'
6 )
7 }
drf的token也有很大的缺点:
在虚拟环境中pip install djangorestframework-jwt
将settings中的REST_FRAMEWORK的TokenAuthentication改成JSONWebTokenAuthentication:
1 REST_FRAMEWORK = {
2 'DEFAULT_AUTHENTICATION_CLASSES': (
3 'rest_framework.authentication.BasicAuthentication',
4 'rest_framework.authentication.SessionAuthentication',
5 # 'rest_framework.authentication.TokenAuthentication'
6 'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
7 )
8 }
然后修改jwt的url:
1 from rest_framework_jwt.views import obtain_jwt_token
2
3 urlpatterns = [
4 path('jwt-auth/', obtain_jwt_token )
5 ]
通过postman发起请求:
vue中登录接口是login:
1 //登录
2 export const login = params => {
3 return axios.post(`${host}/login/`, params)
4 }
后台的接口要与前端保持一致:
1 urlpatterns = [
2 path('login/', obtain_jwt_token ), # jwt-token
3 ]
jwt接口默认采用的是用户名和密码登录验证,如果用手机登录的话,就会验证失败,所以我们需要自定义一个用户验证,在users/view.py中编写:
1 from django.shortcuts import render
2 from django.contrib.auth.backends import ModelBackend
3 from django.contrib.auth import get_user_model
4 from django.db.models import Q
5
6 # Create your views here.
7
8
9 User = get_user_model()
10
11
12 class CustomBackend(ModelBackend):
13 """jwt自定义用户验证"""
14
15 def authenticate(self, request, username=None, password=None, **kwargs):
16 try:
17 user = User.objects.get(Q(username=username) | Q(mobile=username))
18 if user.check_password(password):
19 return user
20 except Exception as e:
21 return None
然后在setting中配置定义好的类:
1 AUTHENTICATION_BACKENDS = (
2 'users.views.CustomBackend',
3 )
jwt过期时间的设置,在setting中配置:
# jwt过期时间
JWT_AUTH = {
'JWT_EXPIRATION_DELTA': datetime.timedelta(days=7), # 也可以设置seconds=20
'JWT_AUTH_HEADER_PREFIX': 'JWT', # JWT跟前端保持一致,比如“token”这里设置成JWT
}
在云片网进行注册,完善开发者信息,然后新增签名和模板,审核通过之后,添加ip白名单,测试的时候使用本地ip,线上部署的时候一定要换成服务器的ip。
然后编写发送验证码的逻辑,在apps下新建utils文件夹,新建yunpian.py文件:
1 import requests
2 import json
3
4
5 class YunPian(object):
6 def __init__(self, api_key):
7 self.api_key = api_key
8 self.single_send_url = 'https://sms.yunpian.com/v2/sms/single_send.json'
9
10 def send_sms(self, code, mobile):
11 # 向云片网发起请求的参数
12 parmas = {
13 "apikey": self.api_key,
14 "mobile": mobile,
15 "text": "【倍思乐】您的验证码是{code}。如非本人操作,请忽略本短信".format(code=code)
16 }
17
18 # 发起请求
19 response = requests.post(self, self.single_send_url, data=parmas)
20 re_dict = json.loads(response.text)
21 return re_dict
22
23
24 # 测试
25 if __name__ == '__main__':
26 yun_pian = YunPian('9b11127a9701975c734b8aee81ee3526')
27 yun_pian.send_sms('2018', '13993601652')
现在开始编写发送短信验证码的接口,首先在settings中配置手机号码的正则表达式:
1 # 手机号码正则表达式
2 REGEX_MOBILE = "^((13[0-9])|(14[5,7])|(15[0-3,5-9])|(17[0,3,5-8])|(18[0-9])|166|198|199|(147))\\d{8}$"
然后对手机号码进行序列化,在users下新建serializers.py:
1 import re
2 from datetime import datetime, timedelta
3
4 from rest_framework import serializers
5 from django.contrib.auth import get_user_model
6
7 from MxShop.settings import REGEX_MOBILE
8 from .models import VerifyCode
9
10 User = get_user_model()
11
12
13 class SmsSerializer(serializers.Serializer):
14 mobile = serializers.CharField(max_length=11)
15
16 # 函数名必须是validate + 验证的字段名
17 def validate_mobile(self, mobile):
18 """手机号验证"""
19
20 # 查询手机号是否已注册
21 if User.objects.filter(mobile=mobile).count():
22 raise serializers.ValidationError('用户已存在')
23
24 # 验证手机号码是否合法
25 if not re.match(REGEX_MOBILE, mobile):
26 raise serializers.ValidationError('手机号码非法')
27
28 # 限制验证码的发送频率,60秒发送一次
29 one_mintes_ago = datetime.now() - timedelta(hours=0, minutes=1, seconds=0)
30 if VerifyCode.objects.filter(add_time__gt=one_mintes_ago, mobile=mobile).count():
31 raise serializers.ValidationError('距离上一次发送未超过60秒')
32
33 return mobile
将云片网的apikey配置到settings中:
1 # 云片网的apikey
2 APIKEY = "xxxxx327d4be01608xxxxxxxxxx"
现在开始完善发送短信验证码的接口:
1 class SmsCodeViewSet(mixins.CreateModelMixin, viewsets.GenericViewSet):
2 """手机验证码"""
3
4 serializer_class = SmsSerializer
5
6 # 随机生成code
7 def generate_code(self):
8 seeds = "1234567890"
9 random_str = []
10 for i in range(4):
11 random_str.append(choice(seeds))
12
13 return "".join(random_str)
14
15 # 重写CreateModelMixin的create方法,加入发送验证码的逻辑
16 def create(self, request, *args, **kwargs):
17 # 验证手机号码
18 serializer = self.get_serializer(data=request.data)
19 serializer.is_valid(raise_exception=True)
20
21 # 发送验证码
22 mobile = serializer.validated_data["mobile"]
23 yun_pian = YunPian(APIKEY)
24 code = self.generate_code()
25 sms_status = yun_pian.send_sms(code=code, mobile=mobile)
26 if sms_status["code"] != 0: # 发送失败
27 return Response({
28 "mobile": sms_status["msg"]
29 }, status=status.HTTP_400_BAD_REQUEST)
30 else:
31 code_record = VerifyCode(code=code, mobile=mobile)
32 code_record.save()
33 return Response({
34 "mobile": mobile
35 }, status=status.HTTP_201_CREATED)
然后注册url:
1 router.register(r'code', SmsCodeViewSet, base_name='code') # 短信验证码
现在开是在接口中进行验证,输入不合法的手机号:
输入合法的手机号后,会发送短信验证码到你的手机。
在编写注册接口之前,需要修改UserProfile中的mobile字段为可以为空,因为前端只有一个值,是username,所以mobile可以为空:
1 class UserProfile(AbstractUser):
2 """用户信息"""
3
4 GENDER_CHOICES = (
5 ("male", u"男"),
6 ("female", u"女")
7 )
8 name = models.CharField("姓名", max_length=30, null=True, blank=True)
9 birthday = models.DateField("出生年月", null=True, blank=True)
10 gender = models.CharField("性别", max_length=6, choices=GENDER_CHOICES, default="female")
11 mobile = models.CharField("电话", max_length=11, null=True, blank=True)
12 email = models.EmailField("邮箱", max_length=100, null=True, blank=True)
13
14 class Meta:
15 verbose_name = "用户信息"
16 verbose_name_plural = verbose_name
17
18 def __str__(self):
19 return self.username
然后编写用户注册的serializer:
1 class UserRegSerializer(serializers.ModelSerializer):
2 # UserProfile中没有code字段,这里需要自定义一个code序列化字段
3 code = serializers.CharField(required=True, write_only=True, max_length=4, min_length=4,
4 error_messages={
5 "blank": "请输入验证码",
6 "required": "请输入验证码",
7 "max_length": "验证码格式错误",
8 "min_length": "验证码格式错误"
9 },
10 help_text="验证码")
11 # 验证用户名是否存在
12 username = serializers.CharField(label="用户名", help_text="用户名", required=True, allow_blank=False,
13 validators=[UniqueValidator(queryset=User.objects.all(), message="用户已经存在")])
14
15 # 验证code
16 def validate_code(self, code):
17 # 用户注册,post方式提交注册信息,post的数据都保存在initial_data里面
18 # username就是用户注册的手机号,验证码按添加时间倒序排序,为了后面验证过期,错误等
19 verify_records = VerifyCode.objects.filter(mobile=self.initial_data["username"]).order_by("-add_time")
20
21 if verify_records:
22 # 最近的一个验证码
23 last_record = verify_records[0]
24 # 有效期为五分钟
25 five_mintes_ago = datetime.now() - timedelta(hours=0, minutes=5, seconds=0)
26 if five_mintes_ago > last_record.add_time:
27 raise serializers.ValidationError("验证码过期")
28
29 if last_record.code != code:
30 raise serializers.ValidationError("验证码错误")
31
32 else:
33 raise serializers.ValidationError("验证码错误")
34
35 # 所有字段。attrs是字段验证合法之后返回的总的dict
36 def validate(self, attrs):
37 # 前端没有传mobile值到后端,这里添加进来
38 attrs["mobile"] = attrs["username"]
39 # code是自己添加得,数据库中并没有这个字段,验证完就删除掉
40 del attrs["code"]
41 return attrs
42
43 class Meta:
44 model = User
45 fields = ('username', 'code', 'mobile')
然后在views.py中编写用户注册的接口:
1 class UserViewSet(mixins.CreateModelMixin, viewsets.GenericViewSet):
2 """用户注册"""
3
4 serializer_class = UserRegSerializer
注册url:
1 router.register(r'users', UserViewSet, base_name='users') # 用户注册
然后在接口中进行测试:
完善用户注册接口:
1 class UserViewSet(mixins.CreateModelMixin, viewsets.GenericViewSet):
2 """用户注册"""
3
4 serializer_class = UserRegSerializer
5 queryset = User.objects.all()
然后在serializers.py中添加密码字段:
1 fields = ('username', 'code', 'mobile', 'password')
需要注意的是密码不能明文显示,需要加密保存, 这是重载Create方法:
1 class UserRegSerializer(serializers.ModelSerializer):
2 # UserProfile中没有code字段,这里需要自定义一个code序列化字段
3 code = serializers.CharField(required=True, write_only=True, max_length=4, min_length=4,
4 error_messages={
5 "blank": "请输入验证码",
6 "required": "请输入验证码",
7 "max_length": "验证码格式错误",
8 "min_length": "验证码格式错误"
9 },
10 help_text="验证码")
11 # 验证用户名是否存在
12 username = serializers.CharField(label="用户名", help_text="用户名", required=True, allow_blank=False,
13 validators=[UniqueValidator(queryset=User.objects.all(), message="用户已经存在")])
14
15 # 输入密码的时候不显示明文
16 password = serializers.CharField(
17 style={'input_type': 'password'}, label=True, write_only=True
18 )
19
20 # 密码加密保存
21 def create(self, validated_data):
22 user = super(UserRegSerializer, self).create(validated_data=validated_data)
23 user.set_password(validated_data["password"])
24 user.save()
25 return user
26
27 # 验证code
28 def validate_code(self, code):
29 # 用户注册,post方式提交注册信息,post的数据都保存在initial_data里面
30 # username就是用户注册的手机号,验证码按添加时间倒序排序,为了后面验证过期,错误等
31 verify_records = VerifyCode.objects.filter(mobile=self.initial_data["username"]).order_by("-add_time")
32
33 if verify_records:
34 # 最近的一个验证码
35 last_record = verify_records[0]
36 # 有效期为五分钟
37 five_mintes_ago = datetime.now() - timedelta(hours=0, minutes=5, seconds=0)
38 if five_mintes_ago > last_record.add_time:
39 raise serializers.ValidationError("验证码过期")
40
41 if last_record.code != code:
42 raise serializers.ValidationError("验证码错误")
43
44 else:
45 raise serializers.ValidationError("验证码错误")
46
47 # 所有字段。attrs是字段验证合法之后返回的总的dict
48 def validate(self, attrs):
49 # 前端没有传mobile值到后端,这里添加进来
50 attrs["mobile"] = attrs["username"]
51 # code是自己添加得,数据库中并没有这个字段,验证完就删除掉
52 del attrs["code"]
53 return attrs
54
55 class Meta:
56 model = User
57 fields = ('username', 'code', 'mobile', 'password')
下面通过信号量的方式来保存密码,在users下新建signals.py文件:
1 from django.dispatch import receiver
2 from django.db.models.signals import post_save
3 from django.contrib.auth import get_user_model
4
5
6 User = get_user_model()
7
8
9 # post_save接收信号的方法, sender接收信号的model
10 @receiver(post_save, sender=User)
11 def create_user(sender, instance=None, created=False, **kwargs):
12 # 是否新建,因为update的时候也会进行post_save
13 if created:
14 # instance相当于user
15 password = instance.password
16 instance.set_password(password)
17 instance.save()
然后在users/apps.py中重载配置:
1 from django.apps import AppConfig
2
3
4 class UsersConfig(AppConfig):
5 name = 'users'
6 verbose_name = "用户管理"
7
8 def ready(self):
9 import users.signals
AppConfig自定义的函数,会在django启动时被运行,现在添加用户的时候,密码就会自动加密存储了。
完善注册接口:
1 class UserViewSet(mixins.CreateModelMixin, viewsets.GenericViewSet):
2 """用户注册"""
3
4 serializer_class = UserRegSerializer
5 queryset = User.objects.all()
6
7 def create(self, request, *args, **kwargs):
8 serializer = self.get_serializer(data=request.data)
9 serializer.is_valid(raise_exception=True)
10
11 user = self.perform_create(serializer)
12 re_dict = serializer.data
13 payload = jwt_payload_handler(user)
14 re_dict["token"] = jwt_encode_handler(payload)
15 re_dict["name"] = user.name if user.name else user.username
16
17 headers = self.get_success_headers(serializer.data)
18 return Response(re_dict, status=status.HTTP_201_CREATED, headers=headers)
19
20 def perform_create(self, serializer):
21 return serializer.save()
然后将Vue中register的接口的host修改:
1 //注册
2
3 export const register = parmas => { return axios.post(`${host}/users/`, parmas) }
然后在注册页面进行测试,发送短信注册成功跳转到首页:
如果没有在云片网审核通过的童靴想要测试接口是否正确,可以先暂时修改发送短信的接口,将随机生成的验证码打印出来,暂时不同云片网发送短信,修改发送短信的接口:
1 class SmsCodeViewSet(mixins.CreateModelMixin, viewsets.GenericViewSet):
2 """手机验证码"""
3
4 serializer_class = SmsSerializer
5
6 # 随机生成code
7 def generate_code(self):
8 seeds = "1234567890"
9 random_str = []
10 for i in range(4):
11 random_str.append(choice(seeds))
12
13 print("".join(random_str))
14
15 return "".join(random_str)
16
17 # 重写CreateModelMixin的create方法,加入发送验证码的逻辑
18 # def create(self, request, *args, **kwargs):
19 # # 验证手机号码
20 # serializer = self.get_serializer(data=request.data)
21 # serializer.is_valid(raise_exception=True)
22 #
23 # # 发送验证码
24 # mobile = serializer.validated_data["mobile"]
25 # yun_pian = YunPian(APIKEY)
26 # code = self.generate_code()
27 # sms_status = yun_pian.send_sms(code=code, mobile=mobile)
28 # if sms_status["code"] != 0: # 发送失败
29 # return Response({
30 # "mobile": sms_status["msg"]
31 # }, status=status.HTTP_400_BAD_REQUEST)
32 # else:
33 # code_record = VerifyCode(code=code, mobile=mobile)
34 # code_record.save()
35 # return Response({
36 # "mobile": mobile
37 # }, status=status.HTTP_201_CREATED)
38
39 # 以下为没有使用云片网
40 def create(self, request, *args, **kwargs):
41 # 验证手机号码
42 serializer = self.get_serializer(data=request.data)
43 serializer.is_valid(raise_exception=True)
44
45 # 获取打印验证码
46 mobile = serializer.validated_data["mobile"]
47 code = self.generate_code()
48
49 code_record = VerifyCode(code=code, mobile=mobile)
50 code_record.save()
51 return Response({
52 "mobile": mobile
53 }, status=status.HTTP_201_CREATED)