文章目录
Web开发 ------ 基于Django+Vue网上购物商城(三)
完整项目地址:https://gitee.com/dadadaliuliuliiu/ShopProject
一、用户认证API接口
但凡是一个公开使用的系统,都离不开认证登录这个模块,接下来我们完成用户登录、注册、注销等用户认证的API接口。
DRF=Django+REST+Framework
1.REST 认证组件
Django 自带一个用户认证系统,这个系统处理用户帐户、组、权限和基于 cookie 的会话。只有认证通过的用户才能访问指定的url地址。
在Rest-Framework
中,认证即是通过继承BaseAuthentication
重构认证的类,认证的逻辑在类的authenticate
方法中实现,通过判断用户传递的信息,如果认证成功返回(用户,用户Token)元组,会将用户对象封装到request里,通过request.用户可以获得用户的相关信息。具体代码实现方式如下:
(1)具体代码
1. INSTALLED_APP 中添加
#ShopProject/setting.py
# Application definition
INSTALLED_APPS = [
#DRF基于token令牌认证的应用
'rest_framework.authtoken',
]
REST_FRAMEWORK = {
# 指定用于支持coreapi的Schema
'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema',
'DEFAULT_AUTHENTICATION_CLASSES': (
# 此身份验证方案使用HTTP基本身份验证,根据用户的用户名和密码进行签名。仅用于测试。
'rest_framework.authentication.BasicAuthentication',
# 此身份验证方案使用Django的默认会话后端进行身份验证。# 如果前后端都是你来写的,那么可以用这个会话认证
'rest_framework.authentication.SessionAuthentication',
# 此身份验证方案使用基于令牌的简单HTTP身份验证方案。令牌认证适用于客户端 - 服务器设置。 #如果是前后端分离,则要用这个令牌认证
'rest_framework.authentication.TokenAuthentication'
)
}
2.token会生成一张表authtoken_token,数据库发生改变,所以要写入数据库
makemigrations
migrate
刷新并查看数据库,产生一条信息
3. 路由配置
from rest_framework.authtoken import views
urlpatterns = [
path('api-token-auth/',views.obtain_auth_token)
]
views.obtain_auth_token源码理解:
obtain_auth_token = ObtainAuthToken.as_view()
返回一个token信息
class ObtainAuthToken(APIView):
# 。。。。。。
def post(self, request, *args, **kwargs):
serializer = self.serializer_class(data=request.data,
context={
'request': request})
serializer.is_valid(raise_exception=True)
user = serializer.validated_data['user']
token, created = Token.objects.get_or_create(user=user)
return Response({
'token': token.key})
obtain_auth_token = ObtainAuthToken.as_view()
(2) postman发送数据 测试
postman下载地址: https://www.postman.com/downloads/
postman新建POST请求操作步骤: https://jingyan.baidu.com/article/e6c8503cb7cd7de54e1a1871.html
token值会保存到数据中,跟这个用户相关联
(3)使用curl命令实现POST提交数据的测试
# -X: 指定HTTP 请求的方法, -d, --data需要提交的表单数据
$ curl -X POST -d "username=admin&password=admin123" http://localhost:8000/api-token-auth/
用我们之前创建的admin用户来测试
模拟浏览器以post方式访问,给我们生成一个token信息
token值会保存到数据中,跟这个用户相关联
(4)REST token认证的缺点
- Token验证是放在一张表中,即authtoken_token中,key没有失效时间,永久有效,一旦泄露,后果不可想象,安全性极差。
- 不利于分布式部署或多个系统使用一套验证,authtoken_token是放在某台服务器上的,如果分布式部署,将失效,或多个系统用一套验证,将必须复制该表到相应服务器上,麻烦费力。
2.REST JWT 认证组件
JSON Web Token(JWT)是目前Token鉴权机制下最流行的方案。
参考资料: 前后端分离之JWT用户认证
(1) 安装模块包及 验证流程
pip install -i https://pypi.douban.com/simple djangorestframework-jwt
Github项目地址: https://github.com/jpadilla/django-rest-framework-jwt
验证流程
用户使用用户名密码来请求服务器
服务器进行验证用户的信息
服务器通过验证发送给用户一个token
客户端存储token,并在每次请求时附送上这个token值
服务端验证token值,并返回数据
(2) 配置文件和路由配置
配置文件中修改认证方式
#setting.py
REST_FRAMEWORK = {
# 指定用于支持coreapi的Schema
'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema',
'DEFAULT_AUTHENTICATION_CLASSES': (
# 此身份验证方案使用HTTP基本身份验证,根据用户的用户名和密码进行签名。仅用于测试。
'rest_framework.authentication.BasicAuthentication',
# 此身份验证方案使用Django的默认会话后端进行身份验证。# 如果前后端都是你来写的,那么可以用这个会话认证
'rest_framework.authentication.SessionAuthentication',
# 此身份验证方案使用基于令牌的简单HTTP身份验证方案。令牌认证适用于客户端 - 服务器设置。 #如果是前后端分离,则要用这个令牌认证
# 'rest_framework.authentication.TokenAuthentication'
'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
)
}
import datetime
JWT_AUTH={
#Token失效时间, 也可以设置seconds=20
'JWT_EXPIRATION_DELTA': datetime.timedelta(days=7),
#Token前缀
'JWT_AUTH_HEADER_PREFIX': 'JWT'
}
路由配置
# jwt的token认证接口
path('jwt-auth/', obtain_jwt_token )
(3) postman发送数据 测试
或者使用curl命令实现POST提交数据的测试
$ curl -X POST -d "username=admin&password=admin123" http://localhost:8000/jwt-auth/
(4)JWT认证存在的问题
JWT接口它默认采用的是用户名和密码登录验证,如果用手机登录的话,就会验证失败,所以我们需要自定义一个用户验证
3.用户手机号登录API接口
(1) settings中配置
ModelBackend跟数据库类型没有关系,这是对用户认证用的。默认的认证backend会对帐号密码进行验证。默认设置如下:
AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.ModelBackend',
)
当然也可以自己扩展,比如用手机号也能登录
AUTHENTICATION_BACKENDS = (
#'django.contrib.auth.backends.ModelBackend',
#也可以自己扩展
'app.users.views.CustomBackend',
)
(2)用户自定义验证
默认认证的是用户名和密码,所以我们对认证进行重写
官方资料: Django 中的自定义验证
#users/views.py
from django.contrib.auth import get_user_model
from django.contrib.auth.backends import ModelBackend
from django.db.models import Q
from django.shortcuts import render
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
(3)postman测试: 能否通过用户名和电话号码登录?
用户名登陆:
电话号码测试:
登陆后台添加电话来测试
- postman测试
得到token信息
- 在浏览器端测试
修改url
#urls.py
urlpatterns = [
path('login/',obtain_jwt_token),
# # 商品列表页, 删除前两种商品列表页的url配置.
# # path('goods/', GoodsListView.as_view(), name='goods-list-rest')
# path('api-token-auth/',views.obtain_auth_token),
# # jwt的token认证接口
# path('jwt-auth/', obtain_jwt_token )
]
浏览器测试
可能报错
电话号码登陆不成功。
原因:配置文件中没有使用自定义的配置文件,或者用户没有手机号码信息。
4.拓展: 短信验证码
很多网站或者手机APP注册时需要手机号来验证身份,验证时需要验证码。本节介绍如何实现验证码功能?
常见的短信验证码服务平台:腾讯云(免费100条/月)、云片网、网易云、阿里云等。
(1)注册
“访问云片网” -> “注册” -> “开发认证” -> “签名管理” -> “模板管理”
还要添加iP白名单,测试就用本地ip,部署的时候一定要换成服务器的ip
步骤如下:
登陆云片网,生成短信的API-key。(开发者认证时长半个小时)
将ip地址加入白名单中
(2)云片网发送短信API说明
(3)发送验证码
#users/sms.py
import json
import string
import random
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):
# 需要传递的参数
parmas = {
"apikey": self.api_key,
"mobile": mobile,
"text": "【刘欢test】您的验证码是{code},如非本人操作,请忽略本短信".format(code=code)
} #注意这里一定要加【刘欢test】,否则会使用子账号默认签名下发
# 向API发送post请求
response = requests.post(self.single_send_url, data=parmas)
# 获取响应数据, 默认响应的信息时json字符串。 {'code':0, 'msg': '发送成功'}
re_dict = json.loads(response.text)
return re_dict
@staticmethod
def generate_code(count=6):
"""生成指定长度验证码"""
return "".join(random.sample(string.digits, count))
if __name__ == '__main__':
api_key = 'd29fcc12744b8208c8383dc2c7f4c457' #这里了在云片网管理控制平台复制APIKEY
yunpian = YunPian(api_key=api_key)
# 生成验证码
code = yunpian.generate_code()
# 发送验证短信
result = yunpian.send_sms(code, '13679127704')
print(result)
#运行结果:
{
'code': 0, 'msg':