json web token方式完成用戶認證
使用方法: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配置
from rest_framework_jwt.views import obtain_jwt_token
urlpatterns = [
.......
url(r'^login/', obtain_jwt_token),
]
(4)postman
http://127.0.0.1:8000/login/

現在就可以登錄了

jwt接口它默認采用的是用戶名和密碼登錄驗證,如果用手機登錄的話,就會驗證失敗,所以我們需要自定義一個用戶驗證
自定義用戶認證
jwt中默認調用的是auth認證中的authticate方法,我們重寫它就好
(1) 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): print(username,password) try: #用戶名和手機都能登錄 user = User.objects.get( Q(username=username) | Q(mobile=username)) if user.check_password(password): return user except Exception as e: return None
(2)settings中配置
AUTHENTICATION_BACKENDS = (
'users.views.CustomBackend',
)
(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,代碼如下:
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) print(response.text) re_dict = json.loads(response.text) return re_dict if __name__ == "__main__": #例如:9b11127a9701975c734b8aee81ee3526 yun_pian = YunPian("*****") yun_pian.send_sms("2018", "180****")
drf實現發送短信驗證碼接口
手機號驗證:
- 是否合法
- 是否已經注冊
(1)settings.py 配置驗證手機號的正則
# 手機號碼正則表達式
REGEX_MOBILE = "^1[358]\d{9}$|^147\d{8}$|^176\d{8}$"
(2)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
可以對序列話中的字段做校驗
def validate_函數名(self, 字段名(這個可以隨意)):
校驗邏輯
return 校驗的字段
(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)
知識點
獲取serializa對象
serializer = self.get_serializer(data=request.data)
#驗證序列話的合法性,如果出錯就會拋出異常
serializer.is_valid(raise_exception=True)
# 從序列化中取出手機號
mobile = serializer.validated_data["mobile"]
(5)配置url
from users.views import SmsCodeViewset # 配置codes的url router.register(r'code', SmsCodeViewset, base_name="code")
開始驗證
輸入不合法的手機號

user serializer 和validator驗證
完成注冊的接口

用戶注冊需要填寫手機號,驗證碼和密碼,相當於create model操作,所以繼承CreateModelMixin
(1)修改UserProfile中mobile字段
mobile = models.CharField("電話",max_length=11,null=True, blank=True)
設置允許為空,因為前端只有一個值,是username,所以mobile可以為空
(2)users/serializers.py添加以下代碼
from rest_framework.validators import UniqueValidator class UserRegSerializer(serializers.ModelSerializer): ''' 用戶注冊 ''' #UserProfile中沒有code字段,這里需要自定義一個code序列化字段 code = serializers.CharField(required=True, write_only=True, max_length=4, min_length=4, error_messages={ "blank": "請輸入驗證碼", "required": "請輸入驗證碼", "max_length": "驗證碼格式錯誤", "min_length": "驗證碼格式錯誤" }, help_text="驗證碼") #驗證用戶名是否存在 username = serializers.CharField(label="用戶名", help_text="用戶名", required=True, allow_blank=False, validators=[UniqueValidator(queryset=User.objects.all(), message="用戶已經存在")]) #驗證code def validate_code(self, code): # 用戶注冊,已post方式提交注冊信息,post的數據都保存在initial_data里面 #username就是用戶注冊的手機號,驗證碼按添加時間倒序排序,為了后面驗證過期,錯誤等 verify_records = VerifyCode.objects.filter(mobile=self.initial_data["username"]).order_by("-add_time") if verify_records: # 最近的一個驗證碼 last_record = verify_records[0] # 有效期為五分鍾。 five_mintes_ago = datetime.now() - timedelta(hours=0, minutes=5, seconds=0) if five_mintes_ago > last_record.add_time: raise serializers.ValidationError("驗證碼過期") if last_record.code != code: raise serializers.ValidationError("驗證碼錯誤") else: raise serializers.ValidationError("驗證碼錯誤") # 所有字段。attrs是字段驗證合法之后返回的總的dict def validate(self, attrs): #前端沒有傳mobile值到后端,這里添加進來 attrs["mobile"] = attrs["username"] #code是自己添加得,數據庫中並沒有這個字段,驗證完就刪除掉 del attrs["code"] return attrs class Meta: model = User fields = ('username','code','mobile')
知識點
1 因為驗證碼code在用戶表中沒有這個字段,我們在注冊用戶的時候需要對這個字段進行判斷后,可以調用 validate 方法把這個字段刪除
2 initial_data里面包含了前端傳送過來的值,我們在使用的時候向字典一樣使用即可如 self.initial_data['username']
3 validators我們可以自己對字段進行靈活的校驗 validators=[UniqueValidator(queryset=User.objects.all(), message="用戶已經存在")]) UniqueValidator 表示該字段是唯一的 里面的參數queryset指的是在那張表中
(3)users/views.py
class UserViewset(CreateModelMixin,viewsets.GenericViewSet):
'''
用戶
'''
serializer_class = UserRegSerializer
queryset = User.objects.all()
(4)配置url
from users.views import UserViewset router.register(r'users', UserViewset, base_name="users")
測試結果如下

django信號量實現用戶密碼修改
user/serializer.py 中的UserRegSerializer添加password
fields = ('username','code','mobile','password')
(2)password不能明文顯示和加密保存
需要重載Create方法
#輸入密碼的時候不顯示明文
password = serializers.CharField(
style={'input_type': 'password'},label=True,write_only=True
)
#密碼加密保存
def create(self, validated_data):
user = super(UserRegSerializer, self).create(validated_data=validated_data)
user.set_password(validated_data["password"])
user.save()
return user
code 中的 write_only的使用:在返回數據的時候不把這個字段做序列化
因為我們把code給刪除掉了,在做序列話的時候會出錯,密碼我們不需要用戶看到,在返回的時候也不要序列化
信號量
(1)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_user(sender, instance=None, created=False, **kwargs):
if created:
password = instance.password
instance.set_password(password)
instance.save()
(2) 在users/app.py
from django.apps import AppConfig
class UsersConfig(AppConfig):
name = 'apps.users'
verbose_name = "用戶管理"
def ready(self):
import users.signals
把 user/serializer.py 中的UserRegSerializer中的這一段代碼注釋掉
#密碼加密保存
def create(self, validated_data):
user = super(UserRegSerializer, self).create(validated_data=validated_data)
user.set_password(validated_data["password"])
user.save()
return user
