06-用户登录和手机注册

一、用户登录和手机注册

1、DRF的token登录和原理

  在前后端不分离中,登录需要添加csrf_token,要进行安全验证,但却在前后端分离中,我们不需要进行csrf_token验证,为什么不用验证呢?因为前端是用APP,安卓来写的,因此这一定是跨站验证,因此不用csrf_token验证,但DRF官方文档给了验证用户方式。

06-用户登录和手机注册

 

 因此将这个配置到项目设置中去。

06-用户登录和手机注册

 

完成上面全局配置,现在来进行tokenTokenAuthentication)的认证模式。

 06-用户登录和手机注册

 

 将这个注册到INSTALLED_APPS中,需要迁移表,因为每注册一个app,都会有对应的表生成。迁移表记录:

06-用户登录和手机注册

 

 

Note: Make sure to run manage.py migrate after changing your settings. The rest_framework.authtoken app provides Django database migrations.


You‘ll also need to create tokens for your users.上面表迁移完,然后官方文档又说需要为我的用户创建tokens

from rest_framework.authtoken.models import Token

token = Token.objects.create(user=...)
print(token.key)

  这个是我们新建的,但我们想要的是用户在进行注册的时候,数据表中就自动创建好这个token,因此我们可以写一个逻辑,当用户注册保存到UserProfile的时候,会自动调用创建token这个逻辑,并完成token的创建。

When using TokenAuthentication, you may want to provide a mechanism for clients to obtain a token given the username and password. REST framework provides a built-in view to provide this behavior. To use it, add the obtain_auth_token view to your URLconf:将我们需要认证的URLconf配置好,让它返回我们的Token给前端,这样前端拿着我们的token就可以进行认证。

from rest_framework.authtoken import views
urlpatterns += [
    url(r‘^api-token-auth/‘, views.obtain_auth_token)
]

06-用户登录和手机注册

 

 配置好了之后,我们来测试我们的接口,测试这个api-token-auth这个接口,需要插件Postman软件进行api请求:

06-用户登录和手机注册

 

后端返回的数据,token密码口令

 06-用户登录和手机注册

06-用户登录和手机注册

 

  数据表中生成token数据,只要用户第一次通个这个api请求,那么会自动生成token数据保存在数据库中,官方文档告诉我们如何使用这个tokenheader中的键是前面的Authorization,值是后面的。

Authorization: Token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b

然后现在用Postman软件的header中添加Authorization键值对,请求,请求来到中间件,每一个中间件都是会对数据进行处理。 要想获取request.userrequest.auth还要在settings中添加:

#配置用户认证
REST_FRAMEWORK = {
    DEFAULT_AUTHENTICATION_CLASSES: [
        rest_framework.authentication.BasicAuthentication,
        rest_framework.authentication.SessionAuthentication,
        rest_framework.authentication.TokenAuthentication,
    ]
}

这样就能获取request.userrequest.auth啦,但这样DRFtoken存在缺点:(1)保存在数据库中,如果是一个分布式服务器,会比较麻烦。(2)token永久有效,没有一个过期时间。

2、json web token原理

  前后端分离之JWT用户认证,前后端分离为啥要认证呢,因为HTTP协议是无状态协议,为了不麻烦,因此我们只需要验证用户是否是登录状态,在传统的方式,是利用CookieSeesion来保存用户的状态,浏览器访问将Cookie带上,然后后端验证Cookie就可以通过或者不通过,前后端分离通过RESTful API进行数据交互时,前端登陆,后端根据用户信息生成一个token,并保存token和对应用户id到数据库或Session中,接着把token传给浏览器,存入浏览器的cookie中,之后浏览器请求带上这个Cookie,后端根据这个cookie值来查询用户,验证是否过期。如果这样的话,页面出现XSS漏洞,由于Cookie可以被JS读取,XSS漏洞会导致用户token泄露(token没有过过期时间)。为了不被JS读取,可以设置httponly,设置securecookie就只允许通过HTTPS传输。secure选项可以过滤掉一些使用HTTP协议的XSS注入,但不能完全阻止。如果将验证数据保存到数据中,这大大增加后台的查询和存储开支,若把验证信息保存在Session中,又加大了服务端的存储压力。

3、json web token方式完成用户认证

 pip install djangorestframework-jwt

 settings.py, 中加入 JSONWebTokenAuthentication 到REST_FRAMEWORK DEFAULT_AUTHENTICATION_CLASSES.

REST_FRAMEWORK = {
    DEFAULT_PERMISSION_CLASSES: (
        rest_framework.permissions.IsAuthenticated,
    ),
    DEFAULT_AUTHENTICATION_CLASSES: (
        rest_framework_jwt.authentication.JSONWebTokenAuthentication,
        rest_framework.authentication.SessionAuthentication,
        rest_framework.authentication.BasicAuthentication,
    ),
}

配置URLconf:

from rest_framework_jwt.views import obtain_jwt_token
#...

urlpatterns = [
    ‘‘,
    # ...

    url(r^api-token-auth/, obtain_jwt_token),
]

利用Postman进行访问

06-用户登录和手机注册

 

 06-用户登录和手机注册

 

 

Now in order to access protected api urls you must include the Authorization: JWT <your_token> header.

$ curl -H "Authorization: JWT <your_token>" http://localhost:8000/protected-url/

 4、vue和jwt接口调试

06-用户登录和手机注册

 

 

 06-用户登录和手机注册

 

 

 然后进行登录,注意这里要求输入的是手机号,因此我们先输入用户名,手机号需要在后端中添加验证逻辑。

06-用户登录和手机注册

 

 

 06-用户登录和手机注册

 

 

 然后发现登录成功啦,接下来,我们需要在后台加上手机号验证逻辑。首先去设置里面设置:

#添加手机验证配置
AUTHENTICATION_BACKENDS = (
    "users.views.CustomBackend",
)

然后来到users.view下编写视图:

from django.shortcuts import render
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()

# Create your views here.

class CustomBackend(ModelBackend):
    """
    自定义用户验证
    """
    def authenticate(self, request, 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

这样就将手机验证传入进来啦。我们DEBUG运行项目,来验证我们定义的手机验证能进入来不,登陆发现可以进入我们的逻辑,按下F8,运行项目,发现登录成功。

06-用户登录和手机注册

 

 

 JWT有很多设置,官方如下说明

Additional Settings

There are some additional settings that you can override similar to how you‘d do it with Django REST framework itself. Here are all the available defaults.

JWT_AUTH = {
    JWT_ENCODE_HANDLER:
    rest_framework_jwt.utils.jwt_encode_handler,

    JWT_DECODE_HANDLER:
    rest_framework_jwt.utils.jwt_decode_handler,

    JWT_PAYLOAD_HANDLER:
    rest_framework_jwt.utils.jwt_payload_handler,

    JWT_PAYLOAD_GET_USER_ID_HANDLER:
    rest_framework_jwt.utils.jwt_get_user_id_from_payload_handler,

    JWT_RESPONSE_PAYLOAD_HANDLER:
    rest_framework_jwt.utils.jwt_response_payload_handler,

    JWT_SECRET_KEY: settings.SECRET_KEY,
    JWT_GET_USER_SECRET_KEY: None,
    JWT_PUBLIC_KEY: None,
    JWT_PRIVATE_KEY: None,
    JWT_ALGORITHM: HS256,
    JWT_VERIFY: True,
    JWT_VERIFY_EXPIRATION: True,
    JWT_LEEWAY: 0,
    JWT_EXPIRATION_DELTA: datetime.timedelta(seconds=300),  #设置过期时间
    JWT_AUDIENCE: None,
    JWT_ISSUER: None,

    JWT_ALLOW_REFRESH: False,
    JWT_REFRESH_EXPIRATION_DELTA: datetime.timedelta(days=7),

    JWT_AUTH_HEADER_PREFIX: JWT,
    JWT_AUTH_COOKIE: None,

}

因为设置太多,具体可以参考JWT的官网设置,我们在项目添加两个重要一点的设置为:

import datetime

JWT_AUTH = {
    JWT_EXPIRATION_DELTA: datetime.timedelta(days=7),  # 设置过期时间
    JWT_AUTH_HEADER_PREFIX: JWT,  #也可以设置Token,要和前端保持一致,我们使用默认就可以
}

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

在这里,要实现手机注册的功能,需要用到第三方发短信的能力,使用云片网可以更好地帮助我们实现。注册登录上面会送0.5毛的短信,因此可以利用来验证我们的项目。

06-用户登录和手机注册

 

 06-用户登录和手机注册

 

 注册的时候,签名需要加上名字后面test签名才能申请通过,必须这样以个人的名义申请,才能申请成功。

 06-用户登录和手机注册

 apps/utils/yunpian.py

import json
import requests


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):
        params = {
            "apikey":self.api_key,
            "mobile":mobile,
            "text":"【李顺涛test】您的验证码是{code}。如非本人操作,请忽略本短信".format(code=code)
        }
        response = requests.post(self.single_send_url,data=params)
        re_dict = json.loads(response.text)
        print(re_dict)


if __name__ == __main__:
    yunpian = YunPian("941ac85a42477492ea781615ca1fecf8")
    yunpian.send_sms("888888","156******17")

 

 06-用户登录和手机注册

 

 测试成功,

 6、发送短信验证码接口

发送验证码的时候需要进行对手机号码的验证,因此前后端分离中用序列器来进行对数据的验证。

users.serializers.py:

import re
from datetime import datetime
from datetime import timedelta
from rest_framework import serializers
from django.contrib.auth import get_user_model
from .models import VerifyCode

from MxShop.settings import REGEX_MOBILE

User = get_user_model()


class SmsSerializer(serializers.Serializer):
    mobile = serializers.CharField(max_length=11)

    def validate_mobile(self, mobile):
        """
        验证手机号码
        :param data:
        :return:
        """
        #手机是否注册
        if User.objects.filter(mobile=mobile).count():
            raise serializers.ValidationError("用户已经存在")
        #验证手机是否合法
        if not re.match(REGEX_MOBILE,mobile):
            raise serializers.ValidationError("手机号码非法")
        #验证码发送频率
        one_minute_ago = datetime.now() - timedelta(hours=0,minutes=1,seconds=0)
        if VerifyCode.objects.filter(add_time__gt=one_minute_ago,mobile=mobile):
            raise serializers.ValidationError("距离上次发送没有到60s")
        return mobile

序列化器写好之后,在视图中编写对手机号码验证,以及数据库查询是否有重复,以及保存到数据库中的逻辑代码:

from rest_framework.mixins import CreateModelMixin
from rest_framework import viewsets
from rest_framework.response import Response
from rest_framework import status
from random import choice

from utils.yunpian import YunPian
from MxShop.settings import APIKEY
from .serializers import SmsSerializer
from .models import VerifyCode


class SmsCodeViewset(CreateModelMixin,viewsets.GenericViewSet):
    """
    发送短信验证码
    """
    serializer_class = SmsSerializer
    def generate_code(self):
        """
        生成四位数字的验证码
        :return:
        """
        sends = "1234567890"
        random_str = []
        for i in range(4):
            random_str.append(choice(sends))

        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)

视图写好之后,配置注册验证码路由:

from users.views import SmsCodeViewset

router = DefaultRouter()

#注册url
router.register(r"codes",SmsCodeViewset,"codes")

06-用户登录和手机注册

06-用户登录和手机注册

 

 06-用户登录和手机注册

 

 验证成功。

7、user serializer和validator验证

users.views.py

class UserViewset(CreateModelMixin,viewsets.GenericViewSet):
    """
    用户
    """
    serializer_class = UserRegisterSerializer

users.serializers.py:

class UserRegisterSerializer(serializers.ModelSerializer):
    code = serializers.CharField(required=True, max_length=4, min_length=4,
                                 error_messages={
                                     "blank": "请输入验证码",
                                     "required": "请输入验证码",
                                     "max_length": "验证码格式错误",
                                     "min_length": "验证码格式错误"
                                 }, help_text="验证码")
    #官网的validators验证
    username = serializers.CharField(required=True, allow_blank=False,
                                     validators=[UniqueValidator(queryset=User.objects.all(),message="用戶已經存在")])

    def validate_code(self, code):
        verify_records = VerifyCode.objects.filter(mobile=self.initial_data["username"]).order_by("-add_time")
        if verify_records:
            last_records = verify_records[0]
            five_minute_ago = datetime.now() - timedelta(hours=0, minutes=5, seconds=0)
            if five_minute_ago < last_records:
                raise serializers.ValidationError("验证码过期")
            if last_records.code != code:
                raise serializers.ValidationError("验证码错误")
        else:
            raise serializers.ValidationError("验证码错误")

    # 作用于所有的serilizers之上,attrs是validtae_data之后返回总的dict
    def validate(self, attrs):
        """
        将必填字段赋值,无用字段删除
        :param attrs:
        :return:
        """
        attrs["mobile"] = attrs["username"]
        del attrs["code"]
        return attrs

    class Meta:
        model = User
        fields = ("username", "code", "mobile")

MxShop.urls.py:

router.register(r"users",UserViewset,"users")

测试:

06-用户登录和手机注册

 

 8、Django信号量实现用户密码修改

users/serializers.py

class UserRegisterSerializer(serializers.ModelSerializer):
    code = serializers.CharField(required=True, write_only=True,max_length=4, min_length=4,label="验证码",
                                 error_messages={
                                     "blank": "请输入验证码",
                                     "required": "请输入验证码",
                                     "max_length": "验证码格式错误",
                                     "min_length": "验证码格式错误"
                                 }, help_text="验证码")
    #官网的validators验证
    username = serializers.CharField(label="用户名",required=True, allow_blank=False,
                                     validators=[UniqueValidator(queryset=User.objects.all(),message="用戶已經存在")])
    password = serializers.CharField(label="密码",write_only=True,
        style={
            "input_type":"password"
        }
    )
    # #密码是明文,重写保存密码为密文  Django的信号量机制可以修改密码(另一种方法)
    # def create(self, validated_data):
    #     user = super(UserRegisterSerializer, self).create(validated_data=validated_data)
    #     user.set_password(validated_data["password"])
    #     user.save()
    #     return user

    def validate_code(self, code):
        verify_records = VerifyCode.objects.filter(mobile=self.initial_data["username"]).order_by("-add_time")
        if verify_records:
            last_records = verify_records[0]
            five_minute_ago = datetime.now() - timedelta(hours=0, minutes=5, seconds=0)
            if five_minute_ago > last_records.add_time:
                raise serializers.ValidationError("验证码过期")
            if last_records.code != code:
                raise serializers.ValidationError("验证码错误")
        else:
            raise serializers.ValidationError("验证码错误")

    # 作用于所有的serilizers之上,attrs是validtae_data之后返回总的dict
    def validate(self, attrs):
        """
        将必填字段赋值,无用字段删除
        :param attrs:
        :return:
        """
        attrs["mobile"] = attrs["username"]
        del attrs["code"]
        return attrs

    class Meta:
        model = User
        fields = ("username", "code", "mobile","password")

上面注释掉的代码可以完成对密码的修改,保存到表中不是明文,而是密文。当然我们为了让代码的分离性更强,利用Django的信号量机制来进行对密码的密文保存,新建users/signals.py:

from django.db.models.signals import post_save
from django.dispatch import receiver
from rest_framework.authtoken.models import Token
from django.contrib.auth import get_user_model

User = get_user_model()

@receiver(post_save, sender=User)
def create_auth_token(sender, instance=None, created=False, **kwargs):
    if created:
        password = instance.password
        instance.set_password(password)
        instance.save()
        # Token.objects.create(user=instance)  用了JWT的方式,就不用Token

        #完成这个我们还要在apps中配置

users/apps.py:

from django.apps import AppConfig


class UsersConfig(AppConfig):
    name = users
    verbose_name = "用户"

    def ready(self):
        import users.signals

完成配置之后运行users接口,post数据。返回如下:

06-用户登录和手机注册

 

 再到数据库中查看信息密码是密文了:

06-用户登录和手机注册

 

 9、vue和注册功能联调

  当注册成功的时候,有两种情况,一种是注册完成之后,自己拿着账号密码在登陆页面登录,另一种情况就是用户注册成功之后自动跳转到首页,并且已经登录,但这会出现情况,当用户注册并跳转手动登录,那么注册的时候,不会反回Token,当注册就自动登录的时候,那么需要后端在注册成功的时候,反回Token

06-用户登录和手机注册

 

   如果是注册成功之后,自动登陆的话,但是后端没有写JWT-Token的接口,因此我们需要到后端去写这个接口,将Token反回到前端。因此我们到users/views.py中的UserViewset中重载CreateModelMixincreate函数。

class UserViewset(CreateModelMixin,viewsets.GenericViewSet):
    """
    用户
    """
    serializer_class = UserRegisterSerializer
    queryset = User.objects.all()

    def create(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        user = self.perform_create(serializer)
        # token返回的时候,是返回serializer.data,因此要放在data里边
        re_dict = serializer.data
        payload = jwt_payload_handler(user)
        #要和前端保持一致,前端也叫token
        re_dict["token"] = jwt_encode_handler(payload)
        re_dict["name"] = user.name if user.name else user.username

        headers = self.get_success_headers(serializer.data)
        return Response(re_dict, status=status.HTTP_201_CREATED, headers=headers)
  #重载上面create中的perform_create方法
  def perform_create(self, serializer): return serializer.save()

06-用户登录和手机注册

 

 

06-用户登录和手机注册

06-用户登录和手机注册

上一篇:美国的电信巨头T-Mobile今天披露了另一起数据遭黑客泄露事件


下一篇:App 自动化框架设计思路