博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
(项目)生鲜超市(六)
阅读量:4699 次
发布时间:2019-06-09

本文共 15837 字,大约阅读时间需要 52 分钟。

七、用户登录与手机注册

1、drf的token

  在INSTALLED_APPS中注册:

1 INSTALLED_APPS = (2     'rest_framework.authtoken'3 )

  然后迁移数据库,会生成一张表authtoken_token,存放用户的token信息:

  配置token的url:

1 from rest_framework.authtoken import views2 3 4 urlpatterns = [5     path('api-token-auth/', views.obtain_auth_token),  # drf-token6 ]

  然后现在测试发起post请求登录,我们使用postman工具来发起请求:

  drf返回的token值会保存到数据库中并与用户进行关联:

  然后客户端需要进行身份验证,令牌密钥包含在 Authorization HTTP header 中。关键字应以字符串文字 “Token” 为前缀,用空格分隔两个字符串。例如:

Authorization: Token 30fc1a3cab2d97a6ab3431d603a0bfc40145785b

  通过验证TokenAuthentication 将提供以下凭据:

  • request.user
  • request.auth

  要想获取这两个实例,还要在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也有很大的缺点:

  • token信息是保存在数据库中的,如果是一个分布式的系统,就比较麻烦
  • token永久有效,没有过期时间

2、json web token方式完成用户认证(JWT)

  在虚拟环境中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_token2 3 urlpatterns = [4     path('jwt-auth/', obtain_jwt_token )5 ]

  通过postman发起请求:

3、Vue和JWT接口调试

  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-token3 ]

  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 user20         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}

4、云片网发送短信验证码

  在云片网进行注册,完善开发者信息,然后新增签名和模板,审核通过之后,添加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_dict22 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 # 云片网的apikey2 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": mobile35             }, status=status.HTTP_201_CREATED)

  然后注册url:

1 router.register(r'code', SmsCodeViewSet, base_name='code')  # 短信验证码

  现在开是在接口中进行验证,输入不合法的手机号:

  输入合法的手机号后,会发送短信验证码到你的手机。

5、注册接口编写

  在编写注册接口之前,需要修改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_name17 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     # 验证code16     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是字段验证合法之后返回的总的dict36     def validate(self, attrs):37         # 前端没有传mobile值到后端,这里添加进来38         attrs["mobile"] = attrs["username"]39         # code是自己添加得,数据库中并没有这个字段,验证完就删除掉40         del attrs["code"]41         return attrs42 43     class Meta:44         model = User45         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')  # 用户注册

  然后在接口中进行测试:

6、django信号量实现用户密码修改

  完善用户注册接口:

1 class UserViewSet(mixins.CreateModelMixin, viewsets.GenericViewSet):2     """用户注册"""3 4     serializer_class = UserRegSerializer5     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=True18 )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 user26 27 # 验证code28 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是字段验证合法之后返回的总的dict48 def validate(self, attrs):49 # 前端没有传mobile值到后端,这里添加进来50 attrs["mobile"] = attrs["username"]51 # code是自己添加得,数据库中并没有这个字段,验证完就删除掉52 del attrs["code"]53 return attrs54 55 class Meta:56 model = User57 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接收信号的model10 @receiver(post_save, sender=User)11 def create_user(sender, instance=None, created=False, **kwargs):12     # 是否新建,因为update的时候也会进行post_save13     if created:14         # instance相当于user15         password = instance.password16         instance.set_password(password)17         instance.save()

  然后在users/apps.py中重载配置:

1 from django.apps import AppConfig2 3 4 class UsersConfig(AppConfig):5     name = 'users'6     verbose_name = "用户管理"7 8     def ready(self):9         import users.signals

  AppConfig自定义的函数,会在django启动时被运行,现在添加用户的时候,密码就会自动加密存储了。

7、Vue和注册接口联调

  完善注册接口:

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.data13         payload = jwt_payload_handler(user)14         re_dict["token"] = jwt_encode_handler(payload)15         re_dict["name"] = user.name if user.name else user.username16 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": mobile37 # }, 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": mobile53 }, status=status.HTTP_201_CREATED)

 

转载于:https://www.cnblogs.com/Sweltering/p/10017611.html

你可能感兴趣的文章
软件工程设计之四则运算
查看>>
SpringMVC @ResponseBody 406
查看>>
HDOJ---2824 The Euler function[欧拉函数]
查看>>
KMP算法
查看>>
Atlas学习之开始篇[转]
查看>>
第二章 在HTML页面里使用javaScript
查看>>
【Educational Codeforces Round 48 (Rated for Div. 2) D】Vasya And The Matrix
查看>>
正则表达式的性能评测
查看>>
CF1172B Nauuo and Circle
查看>>
CF1178D Prime Graph
查看>>
CF1190D Tokitsukaze and Strange Rectangle
查看>>
CF1202F You Are Given Some Letters...
查看>>
CF1179C Serge and Dining Room
查看>>
CF1168B Good Triple
查看>>
CF1208E Let Them Slide
查看>>
AT2000 Leftmost Ball
查看>>
CF1086E Beautiful Matrix
查看>>
在单位上班的25条建议(建议收藏)
查看>>
web前端--http协议
查看>>
欧拉定理证明&阶乘的逆元
查看>>