一:用户名登录和用户名展示:
# 用户名登陆接口实现
class LoginView(View):
def post(self, request):
data = json.loads(request.body.decode())
username = data.get("username")
password = data.get("password")
remembered = data.get("remembered")
user = authenticate(request, username=username, password=password)
if user is None:
return JsonResponse({
"code": 400,
"errmsg": "用户名或者密码错误"
})
login(request, user)
if remembered != True:
request.session.set_expiry(0)
else:
request.session.set_expiry(None)
response = JsonResponse({
"code": 0,
"errmsg": "ok"
})
response.set_cookie('username', user.username, max_age=3600 * 24 * 14)
return response
视图:
re_path(r"^login/$", views.LoginView.as_view()),
二:多账号登录:(自定义认证后端)
users/utils
from django.contrib.auth.backends import ModelBackend
from django.db.models import Q
from apps.users.models import User
class LoginBackend(ModelBackend):
def authenticate(self, request, username=None, password=None, **kwargs):
try:
user = User.objects.get(Q(username=username)|Q(mobile=username))
except Exception as e:
return None
if user.check_password(password):
return user
配置文件中:
AUTHENTICATION_BACKENDS = ['apps.users.utils.LoginBackend']
三:退出登录:
class LoginOutView(View):
def delete(self, request):
# 1:清理session
logout(request)
response = JsonResponse({"code": 0, "errmsg": "ok"})
# 2:清除前端的cookie
response.delete_cookie("username")
return response
四:用户中心页面展示:(自定义登录后端)
用户登陆了,可以访问用户中心页面,没有登录不允许访问,怎样判断用户是否登录了呢??
from django.contrib.auth.mixins import LoginRequiredMixin
from django.http import JsonResponse
class LoginRequiredJSONMixin(LoginRequiredMixin):
def handle_no_permission(self):
return JsonResponse({'code': 400, 'errmsg': '用户未登录'})
# 用户中心页面展示:
class UserCenterDataView(LoginRequiredJSONMixin, View):
def get(self, request):
return JsonResponse({
'code': 0,
'errmsg': '个人中心',
"info_data":{
"username" : request.user.username,
"mobile" : request.user.mobile,
"email" : request.user.email,
"email_active" : request.user.email_active
}
})
======================================================================
QQ登录:
一:子应用准备工作:
1:建立一个子应用:oauth,注册子应用,总路由和子路由
1:建一个子应用
(django_env) python@ubuntu:~/Desktop/meiduo_project/meiduo_mall/apps$ python3 ../manage.py startapp oauth
2:注册子应用,dev.py中
INSTALLED_APPS = [
'apps.oauth',
]
3:注册总路由:meiduo_mall/urls
urlpatterns = [
re_path("", include("apps.oauth.urls")),
]
4:注册子路由:apps/oauth/urls
urlpatterns = [
]
二:QQ登陆的流程分析:
QQ登录有三个过程:
过程一:用户点击美多商城的QQ登录,此时没多商城后台向QQ申请QQ登录的url,QQ将QQ登录的链接返回给美多商城,美多商城再把这个链接返回给用户。
中间过程:用户扫码登录QQ,QQ返回给用户浏览器一个带有用户身份的code。
过程二: 用户拿到这个code后,再次请求美多商城。美多商城根据code向QQ要access_tocken,QQ返回给美多商城access_tocken。美多商城根据access_tocken 再向QQ要openid。最终美多商城拿到用户的openid。
过程三:过程二已经确认用户的身份信息,现在美多商城根据openid查找用户,如果找到用户,则构建响应,状态保持即可。如果在数据库中没有对应的人,则返回填写用户信息的页面。用户填完信息,提交到美多商城后台有两种情况:
情况一:用户从来没有注册过美多商城,则新建用户,并且绑定用户的openid。
情况二:用户已经注册过美多商城了,但是从来没有绑定过QQ,则直接绑定用户的openid即可。
三:模型类准备工作:
1:创建模型类的基类(因为每一个模型类都会有创建时间和更新时间):meiduo_mall/utils/models.py
from django.db import models
class BaseModel(models.Model):
"""为模型类补充字段"""
# 创建时间:
create_time = models.DateTimeField(auto_now_add=True, verbose_name="创建时间")
# 更新时间:
update_time = models.DateTimeField(auto_now=True, verbose_name="更新时间")
class Meta:
# 说明是抽象模型类(抽象模型类不会创建表)
abstract = True
auto_now_add:创建或添加对象时自动添加时间, 修改或更新对象时, 不会更改时间
auto_now:凡是对对象进行操作(创建/添加/修改/更新),时间都会随之改变
2:定义登录使用的模型类:oauth/models.py
from django.db import models
from meiduo_mall.utils.models import BaseModel
# 定义QQ登录的模型类:
class OAuthQQUser(BaseModel):
"""QQ登录用户数据"""
# user 是个外键, 关联对应的用户
user = models.ForeignKey('users.User', on_delete=models.CASCADE, verbose_name='用户')
# qq 发布的用户身份id
openid = models.CharField(max_length=64, verbose_name='openid', db_index=True)
class Meta:
db_table = 'tb_oauth_qq'
3:迁移建表:
(django_env) python@ubuntu:~/Desktop/meiduo_project/meiduo_mall$ python manage.py
(django_env) python@ubuntu:~/Desktop/meiduo_project/meiduo_mall$ python manage.py migrate
四:QQ登录工具的安装和参数的配置:
pip install QQLoginTool -i https://pypi.tuna.tsinghua.edu.cn/simple
dev.py:QQ登录参数
# QQ登录参数
# 我们申请的 客户端id
QQ_CLIENT_ID = '101474184'
# 我们申请的 客户端秘钥
QQ_CLIENT_SECRET = 'c6ce949e04e12ecc909ae6a8b09b637c'
# 我们申请时添加的: 登录成功后回调的路径
QQ_REDIRECT_URI = 'http://www.meiduo.site:8080/oauth_callback.html'
五:功能实现:
1:第一个视图:获取openid : oauth/views.py
from QQLoginTool.QQtool import OAuthQQ
from django.conf import settings
from django.http import JsonResponse
from django.views import View
class QQURLView(View):
def get(self, request):
# 1:登陆成功后跳转的网页
next = request.GET.get("next")
# 2: 创建QQ登陆对象
oauth = OAuthQQ(client_id=settings.QQ_CLIENT_ID,
client_secret=settings.QQ_CLIENT_SECRET,
redirect_uri=settings.QQ_REDIRECT_URI,
state=next)
# 3:使用QQ对象拿到QQ返回的url
login_url = oauth.get_qq_url()
return JsonResponse({
"code": 0,
"errmsg": "ok",
"login_url": login_url,
})
路由:
from django.urls import path
from oauth import views
urlpatterns = [
path('qq/authorization/', views.QQURLView.as_view()),
]
2:第二个视图:
2.1:根据code -->access_tocken ----> openid
class QQUserView(View):
def get(self, request):
# 1:获取code
code = request.GET.get("code")
if not code:
return JsonResponse({
"code": 400,
"errmsg": "缺失参数"
})
# 2:创建QQ登陆对象:
oauth = OAuthQQ(client_id=settings.QQ_CLIENT_ID,
client_secret=settings.QQ_CLIENT_SECRET,
redirect_uri=settings.QQ_REDIRECT_URI,
state=next)
# 3:获取openid
try:
access_token = oauth.get_access_token(code)
openid = oauth.get_open_id(access_token)
except Exception as e:
return JsonResponse({
"code": 400,
"errmsg": "无法获取用户的access_tocken",
})
else:
pass
2.2:如果用户存在则状态保持,如果不存在则将openid加密返回给用户。
else:
# 4:根据openid判断用户是否绑定过用户
try:
oauth_qq = OAuthQQUser.objects.get(openid=openid)
except Exception as e:
# 用户没有绑定美多商城用户,使用加密类加密用户的openid,返回给用户,前端会让顾客填写信息,然后再带着这个openid返回
pass
else:# 4.1: 数据库中查询到了用户
user = oauth_qq.user
# 状态保持与设置cookie
login(request, user)
response = JsonResponse({
"code": 0,
"errmsg": "ok"
})
response.set_cookie("username", user.username, max_age=3600 * 24 * 14)
return response
此时出现一个问题:需要对openid进行加密,下一次用户再带回来时,需要解密。这就需要加密解密的类。
2.3:itsdangerous加密模块的使用:
安装:pip install itsdangerous -i https://pypi.tuna.tsinghua.edu.cn/simple
meiduo_mall.utils.secret.py
from django.conf import settings
# 使用 TimedJSONWebSignatureSerializer 对象 可以设置有效期
from itsdangerous import TimedJSONWebSignatureSerializer as Serializer
class SecretOauth(object):
def __init__(self):
# 1.根据 签名(secret_key) 创建序列化对象
self.serializer = Serializer(secret_key=settings.SECRET_KEY, expires_in=24 * 15 * 60)
# 加密
def dumps(self, content_dict):
# 1. 通过 dumps方法 加密数据
result = self.serializer.dumps(content_dict)
# 2. result是bytes类型转换成 str
return result.decode()
# 解密
def loads(self, content_dict):
try:
# 1. 通过 loads方法 解密数据
result = self.serializer.loads(content_dict)
except Exception as e:
print(e)
# 解密失败返回None
return None
# 2. 返回解密完毕数据
return result
2.4:使用加密类加密返回:
try:
oauth_qq = OAuthQQUser.objects.get(openid=openid)
except Exception as e:
# 用户没有绑定美多商城用户,使用加密类加密用户的openid,返回给用户,前端会让顾客填写信息,然后再带着这个openid返回
access_token = SecretOauth().dumps({'openid': openid})
# 注意: 这里一定不能返回 0 的状态码. 否则不能进行绑定页面
return JsonResponse({'code': 300, 'errmsg': 'ok', 'access_token': access_token})
2.5:完整的视图:
from QQLoginTool.QQtool import OAuthQQ
from django.conf import settings
from django.contrib.auth import login
from django.http import JsonResponse
from django.views import View
from meiduo_mall.utils.secret import SecretOauth
from apps.oauth.models import OAuthQQUser
class QQURLView(View):
def get(self, request):
# 1:登陆成功后跳转的网页
next = request.GET.get("next")
# 2: 创建QQ登陆对象
oauth = OAuthQQ(client_id=settings.QQ_CLIENT_ID,
client_secret=settings.QQ_CLIENT_SECRET,
redirect_uri=settings.QQ_REDIRECT_URI,
state=next)
# 3:使用QQ对象拿到QQ返回的url
login_url = oauth.get_qq_url()
return JsonResponse({
"code": 0,
"errmsg": "ok",
"login_url": login_url,
})
class QQUserView(View):
def get(self, request):
# 1:获取code
code = request.GET.get("code")
if not code:
return JsonResponse({
"code": 400,
"errmsg": "缺失参数"
})
# 2:创建QQ登陆对象:
oauth = OAuthQQ(client_id=settings.QQ_CLIENT_ID,
client_secret=settings.QQ_CLIENT_SECRET,
redirect_uri=settings.QQ_REDIRECT_URI,
state=next)
# 3:获取openid
try:
access_token = oauth.get_access_token(code)
openid = oauth.get_open_id(access_token)
except Exception as e:
return JsonResponse({
"code": 400,
"errmsg": "无法获取用户的access_tocken",
})
else:
# 4:根据openid判断用户是否绑定过用户
try:
oauth_qq = OAuthQQUser.objects.get(openid=openid)
except Exception as e:
# 用户没有绑定美多商城用户,使用加密类加密用户的openid,返回给用户,前端会让顾客填写信息,然后再带着这个openid返回
access_token = SecretOauth().dumps({'openid': openid})
# 注意: 这里一定不能返回 0 的状态码. 否则不能进行绑定页面
return JsonResponse({'code': 300, 'errmsg': 'ok', 'access_token': access_token})
else:# 4.1: 数据库中查询到了用户
user = oauth_qq.user
# 状态保持与设置cookie
login(request, user)
response = JsonResponse({
"code": 0,
"errmsg": "ok"
})
response.set_cookie("username", user.username, max_age=3600 * 24 * 14)
return response
2.6:路由:
path('oauth_callback/', views.QQUserView.as_view()),
3:第二个视图的第二部分:
核心代码:
# 4.保存注册数据
try:
user = User.objects.get(mobile=mobile)
except Exception as e:
# 用户不存在,新建用户
user = User.objects.create_user(username=mobile,
password=password,
mmobile=mobile)
else:
# 如果用户存在,检查用户密码
if not user.check_password(password):
return JsonResponse({'code': 400,
'errmsg': '输入的密码不正确'})
# 5.将用户绑定 openid
try:
OAuthQQUser.objects.create(openid=openid,
user=user)
except DatabaseError:
return JsonResponse({'code': 400,
'errmsg': '往数据库添加数据出错'})
总体代码:
def post(self, request):
"""美多商城用户绑定到openid"""
# 1.接收参数
data_dict = json.loads(request.body.decode())
mobile = data_dict.get('mobile')
password = data_dict.get('password')
sms_code_client = data_dict.get('sms_code')
access_token = data_dict.get('access_token')
# 2.校验参数
# 判断参数是否齐全
if not all([mobile, password, sms_code_client]):
return JsonResponse({'code': 400,
'errmsg': '缺少必传参数'})
# 判断手机号是否合法
if not re.match(r'^1[3-9]\d{9}$', mobile):
return JsonResponse({'code': 400,
'errmsg': '请输入正确的手机号码'})
# 判断密码是否合格
if not re.match(r'^[0-9A-Za-z]{8,20}$', password):
return JsonResponse({'code': 400,
'errmsg': '请输入8-20位的密码'})
# 3.判断短信验证码是否一致
# 创建 redis 链接对象:
redis_conn = get_redis_connection('verify_code')
# 从 redis 中获取 sms_code 值:
sms_code_server = redis_conn.get('sms_%s' % mobile)
# 判断获取出来的有没有:
if sms_code_server is None:
# 如果没有, 直接返回:
return JsonResponse({'code': 400,
'errmsg': '验证码失效'})
# 如果有, 则进行判断:
if sms_code_client != sms_code_server.decode():
# 如果不匹配, 则直接返回:
return JsonResponse({'code': 400,
'errmsg': '输入的验证码有误'})
# 调用我们自定义的函数, 检验传入的 access_token 是否正确:
# 错误提示放在 sms_code_errmsg 位置
openid = SecretOauth().loads(access_token).get('openid')
print(openid)
if not openid:
return JsonResponse({'code': 400,
'errmsg': '缺少openid'})
# 4.保存注册数据
try:
user = User.objects.get(mmobile=mobile)
except Exception as e:
# 用户不存在,新建用户
user = User.objects.create_user(username=mobile,
password=password,
mmobile=mobile)
else:
# 如果用户存在,检查用户密码
if not user.check_password(password):
return JsonResponse({'code': 400,
'errmsg': '输入的密码不正确'})
# 5.将用户绑定 openid
try:
OAuthQQUser.objects.create(openid=openid,
user=user)
except DatabaseError:
return JsonResponse({'code': 400,
'errmsg': '往数据库添加数据出错'})
# 6.实现状态保持
login(request, user)
# 7.创建响应对象:
response = JsonResponse({'code': 0,
'errmsg': 'ok'})
# 8.登录时用户名写入到 cookie,有效期14天
response.set_cookie('username',
user.username,
max_age=3600 * 24 * 14)
# 9.响应
return response
六:测试工作:
6.1:用户没有注册过美多,直接QQ登录。
首先关闭云通讯,改成打印方式:(不浪费钱,云通讯前面已经实现了)
# CCP().send_template_sms(
# mobile,
# [sms_code, 10],
# 1,
# )
print(sms_code)
删除数据库tb_user所有的字段
点击QQ登录:
扫码登录:
注册登录:
结果:
6.2:现在有了账号直接扫码验证,则现象是可以直接登录进去。
就是用刚才QQ再次登录。
6.3:如果原来有美多商城账号,现在用QQ登录呢?
先注册一个美多账号,没有关联QQ。
此时user表中有两个数据:
但是oauth_qq这个关联QQ的表没有第二个数据:
再次QQ登录
进入成功
出现两条记录。