django 自定义登录与认证
2022年7月11日大约 4 分钟约 1154 字
JSON Web Token 认证
jwt的字符串有两块:前面是base64编码的用户信息,后面是签名。签名只有后端能解码,用于识别伪造的token。
session认证方式一般用于前后端不分离的场景,session值是保存在内存中 drf内置的token认证功能过于简单,token值保存到数据库的表中,没有过期时间设置 jwt认证方式是普通token升级版,用户信息+后端签名作为token发给前端保存。签名解密密钥只有后端有,也不存在token被篡改。无须保存用户状态,由前端保存
文档:点击查看
- 安装包
pip install djangorestframework-simplejwt
- 配置
settings.py
from datetime import timedelta
INSTALLED_APPS = [
# ...
'rest_framework_simplejwt', # 使用jwt做用户认证
# ...
]
REST_FRAMEWORK = {
# ...
'DEFAULT_AUTHENTICATION_CLASSES': ( # 类似于中间件,按顺序执行authenticate(),主要是通过不同的认证方式来查找用户并设置request.user
# ...
'rest_framework.authentication.BasicAuthentication',
'rest_framework.authentication.SessionAuthentication', # 依赖django的SessionMiddleware、AuthenticationMiddleware,一般浏览器常见,前后端分离一般不用,不过内置文档功能要用
# 'rest_framework_simplejwt.authentication.JWTAuthentication', # 这里是全局做 jwt接收到request.user的转换,有些api是不需要登录的,所以一般也不在这配,仅作演示
)
# ...
}
SIMPLE_JWT = {
'ACCESS_TOKEN_LIFETIME': timedelta(days=7),
urls.py
from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView, TokenVerifyView
urlpatterns = [
...
path('token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
path('token/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
path('token/verify/', TokenVerifyView.as_view(), name='token_verify'),
...
]
persons/views.py
# 全局配置取消,ViewSet单独配置需要登录才能访问该视图集,如果不配置就无须认证
from rest_framework_simplejwt.authentication import JWTAuthentication
class PersonListViewSet(...):
authentication_classes = (JWTAuthentication, )
- 前端获取token测试
# ------登录-------
发送post请求:http://localhost:8000/token/
post数据:{"username": "xiongda", "password": "123456"}
request头:"Content-Type: application/json"
# ------使用-------
在使用时,在request头部添加 Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI...
自定义用户验证
使用手机短信验证
apps/utils/send_sms.py
这里是发送短信的api
import json
class SendSMS():
def __init__(self, api_key):
self.api_key = api_key
self.send_url = "https://sms.aliyun.com/...."
def send_verification_code(self, code, phone):
data = {
"apikey": self.api_key,
"mobile": phone,
"text": "[签名]您的验证码是{code},请不要告诉他人".format(code=code)
}
response = requests.post(self.send_url, data=data)
r_dict = json.loads(response.text)
print(r_dict)
return r_dict
if __name__ == "__main__":
send_sms = SendSMS("dhw2nbfh35j43rbd35irdfj")
send_sms.send_verification_code("1234", "13928374837")
users/models.py
保存短信验证码的数据表
class VerifyCode(models.Model):
"""
短信验证码表
"""
code = models.CharField(max_length=10, verbose_name="验证码")
phone = models.CharField(max_length=11, verbose_name="电话")
add_time = models.DateTimeField(default=datetime.now, verbose_name="添加时间")
users/serializers.py
验证码表的序列化器
import re
from datetime import datetime, timedelta
from django.contrib.auth import get_user_model
from .models import VerifyCode
import random
UserModel = get_user_model()
# 短信验证码表的序列化器
class SmsSerializer(serializers.Serializer):
phone = serializers.CharField(max_length=11)
# 验证手机号是否可发送验证码
def validate_phone(self, phone):
# 手机是否注册
if UserModel.objects.filter(phone=phone).count():
raise serializers.ValidationError("用户已经存在")
# 验证手机号是否合法
if not re.match("^1[358]\{9}$ | ^147\d{8}$ | ^176\d{8}$", phone):
raise serializers.ValidationError("非法手机号")
# 验证发送频率
before_datetime = datetime.now() - timedelta(hours=0, minutes=1, seconds=0)
if VerifyCode.objects.filter(add_time__gt = before_datetime, phone=phone):
raise serializers.ValidationError("请间隔1分钟再次发送")
return phone
# 生成随机验证码
def generate_code(self):
seeds = "1234567890"
random_str = []
for i in range(4):
random_str.append(choice(seeds))
return "".join(random_str)
# 用户表的序列化器
class UserSerializer(serializers.ModelSerizlier):
code = serializers.CharField(max_length=4, min_length=4, required=True)
def validate_code(self, code):
verify_records = VerifyCode.object.filter(phone=self.inital_data['username']).order_by('-add_time')
before_datetime = datetime.now() - timedelta(hours=0, minutes=1, seconds=0)
# ...
# 这里是识别验证码是否在数据库中存在和有效,代码没写完,太多了
class Meta:
model = UserModel
fields = ('username', 'code', 'phone')
users/views.py
发送短信的视图集
from django.contrib.auth.backends import ModelBackend
from django.contrib.auth import get_user_model
from django.db.models import Q
from rest_framework.mixins import CreateModelMixin
from rest_framework import viewsets
from .serializers import SmsSerializer
from utils.send_sms import SendSMS
UserModel = get_user_model()
class CustomBackend(ModelBackend):
def authenticate(self, username=None, password=None, **kwargs):
try:
user = UserModel.objects.get(Q(username=username)|Q(phone=username))
if user.check_password(password):
return user
except Exception as e:
return None
# 发送短信验证码
class SmsCodeViewSet(viewsets.GenericViewSet ,CreateModelMixin):
serializer_class = SmsSerializer
# 拷贝CreateModelMixin方法,重写
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True) # 有异常时返回400
phone = serializer.validated_data['phone']
send_sms = SendSMS("sdgso3inas030sa")
sms_status = send_sms.send_sms(code=self.generate_code(), phone=phone)
if sms_status['code'] != 0:
return Response({ "phone": sms_status['msg'] }, status=status.HTTP_400_BAD_REQUEST)
else:
code_record = VerifyCode(code=code, phone=phone)
code_record.save() # 发送成功再保存验证码状态
return Response({ "phone": phone }, status=status.HTTP_201_CREATED)
settings.py
配置自定义验证
# 在settings中配置
AUTHENTICATION_BACKENDS = (
'users.views.CustomBackend',
)
urls.py
添加url路由
router.register(r'codes', SmsCodeViewSet, basename='codes')
对象级别的权限
persons/permissions.py
from rest_framework import permissions
class IsOwnerOrReadOnly(permissions.BasePermission):
"""
自定义权限,只允许对象的所有者编辑它,确保只有创建该数据的用户才能更新或删除它
"""
def has_object_permission(self, request, view, obj):
# 读取权限允许任何请求,所以我们总是允许GET,HEAD或OPTIONS请求。
if request.method in permissions.SAFE_METHODS:
return True
# 只有该person的所有者才允许写权限。
return obj.owner == request.user