手机登录和注册
1.首先我们需要在settings中配置Session
https://www.django-rest-framework.org/api-guide/authentication/
2.配置TokenAuthentication
INSTALLED_APPS = (
...
'rest_framework.authtoken'
)
执行数据库操作
1.makemigrations
2.migrate
执行完毕之后会创建authtoken_token表
3.手动为用户创建token
url中配置
from rest_framework.authtoken import views
# 该url用于在用户POST提交用户名密码之后返回对应的token
url(r'^api-token-auth/', views.obtain_auth_token)
4.通过token从后台获取用户信息
setting中配置
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.BasicAuthentication',
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.TokenAuthentication',
)
}
- 前端发送请求Header中配置
Authorization: Token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b
token的实现机制
token的机制是,当用户使用POST发送请求后,tokeauth会调用get_create_token方法向表中插入一条数据,并将token返回给前端,然后用户下次请求时将token按照规定的格式放入headers中,后台就可以解析到requset中的token从而拿到相应的用户了,request._auth记录的是token,requset._user记录的是用户对象
Token验证不能全局配置,因为验证失败时,会返回401,造成不好的用户体验。所以应该把token验证放在view中。像这样
views
authentication_classes = (TokenAuthentication,)
JWT用户认证(Json Web Token)
https://www.cnblogs.com/wenqiangit/p/9592132.html
XSS漏洞 https://www.freebuf.com/column/155799.html
对称加密 https://www.jianshu.com/p/3840b344b27c?utm_campaign=maleskine&utm_content=note&utm_medium=seo_notes&utm_source=recommendation
单点登录 https://blog.csdn.net/qq_39089301/article/details/80615348
使用Djano JWT
https://github.com/GetBlimp/django-rest-framework-jwt
- 安装
pip install djangorestframework-jwt
- settings中配置
# 全局认证,开源jwt,对用户POST的用户名密码进行验证,之后,解析拿到user
'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
- url中配置
# jwt的认证接口
url(r'^jwy_auth/', obtain_jwt_token),
测试,POST
返回token,前面是BASE64,后面是对其加密后的签名
{
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjo2LCJ1c2VybmFtZSI6Imh1b3RvbmciLCJleHAiOjE1NTkxOTY4NjMsImVtYWlsIjoiODI0MDExMTQyQHFxLmNvbSJ9.lHqC6RvFBFaQuQOGy-N-fHSRcOOpjJ1_3_8Z7MBYRns"
}
带着Token发送get
请求头:
Authorization:JWT eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjo2LCJ1c2VybmFtZSI6Imh1b3RvbmciLCJleHAiOjE1NTkxOTc2NDUsImVtYWlsIjoiODI0MDExMTQyQHFxLmNvbSJ9.ecETXIP8PJEAVfjXCNi-ucf6ifOrCvqwpAsaq2bUbKk
Debug后台就能从request中拿到用户数据
因为该token验证的时候只会验证用户名和密码,如果是手机验证的化需要自定义验证类
- 自定义验证类(继承modelbackend,重写authenticate验证方法)
User = get_user_model()
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
- settings中配置类
AUTHENTICATION_BACKENDS = (
'consumers.views.CustomBackend',
)
配置JWT的全局设置
import datetime
JWT_AUTH = {
'JWT_EXPIRATION_DELTA': datetime.timedelta(days=7), # 过期时间
'JWT_AUTH_HEADER_PREFIX': 'JWT', # Headre中的前缀
}
?: (guardian.W001) Guardian authentication backend is not hooked. You can add this in settings as eg: AUTHENTICATION_BACKENDS = ('django.contrib.auth.backends.ModelBackend', 'guardian.backends.ObjectPermissionBackend')
.
使用SubMail进行短信发送
f0585eb5bd93c61ab719689054596653
# 56af256fd068bab643a1088e2a145d83
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】huotong:您的验证码是{code}。如非本人操作,请忽略本短信".format(code=code)
}
response = requests.post(self.single_send_url, data=params)
re_dict = json.loads(response.text)
return re_dict
if __name__ == "__main__":
...
SMSSerializer验证验证码
class SMSSerializer(serializers.Serializer):
mobile = serializers.CharField(max_length=11)
def validate_mobile(self, mobile):
'''
验证手机号码
:param mobile:
:return:
'''
# 手机号是否注册
if User.objects.filter(mobile=mobile).count():
raise serializers.ValidationError("用户已经存在")
# 验证手机号是否合法
if not re.match(settings.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).count():
raise serializers.ValidationError('请超过60s后再次发送')
return mobile
ConsumerRegSerializer 验证用户
class ConsumerRegSerializer(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="验证码")
username = serializers.CharField(label="用户名", help_text="用户名", required=True, allow_blank=False,
validators=[UniqueValidator(queryset=User.objects.all(), message="用户已经存在")])
password = serializers.CharField(
style={'input_type': 'password'},help_text="密码", label="密码", 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
def validate_code(self, code):
# try:
# verify_records = VerifyCode.objects.get(mobile=self.initial_data["username"], code=code)
# except VerifyCode.DoesNotExist as e:
# pass
# except VerifyCode.MultipleObjectsReturned as e:
# pass
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("验证码错误")
def validate(self, attrs):
attrs["mobile"] = attrs["username"] # 整体验证
del attrs["code"]
return attrs
class Meta:
model = User
fields = ("username", "code", "mobile", "password")
注册用户时候的表单View
class ConsumerViewset(CreateModelMixin, mixins.UpdateModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet):
"""
用户注册时候的验证
"""
serializer_class = ConsumerRegSerializer
queryset = User.objects.all()
# 也可以这样加密保存密码
# def create(self, validated_data):
# user = super(ConsumerRegSerializer,self).create(validated_data=validated_data)
# user.set_password(validated_data["password"])
# user.save()
# return user
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
user = self.perform_create(serializer)
# 这一步会序列化对象,所有fields中的字段都会被序列号,而code已经被删除了
# ,所以code中加入了write_only=True,让他不要被序列化和返回前端
# register的用户对象
re_dict = serializer.data
# 将token返回给前端
payload = jwt_payload_handler(user)
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)
def get_object(self):
return self.request.user
# 重写次方法,返回user
def perform_create(self, serializer):
# 这里需要操作数据库,而密码需要加密,所以用到了信号量
# 在每次post保存的时候,将密码加密
return serializer.save()
使用信号量加密保存密码
- singals.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()
# sender就是一个要操作的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()
- apps.py注册
from django.apps import AppConfig
class ConsumersConfig(AppConfig):
name = 'consumers'
def ready(self):
import users.signals