QQ登录:即我们所说的第三方登录,是指用户可以不在本项目中输入密码,而直接通过第三方的验证,成功登录本项目。
若想实现QQ登录,需要成为QQ互联的开发者,审核通过才可实现。
相关连接:http://wiki.connect.qq.com/成为开发者成为QQ互联开发者后,还需创建应用,即获取本项目对应与QQ互联的应用ID。
相关连接:http://wiki.connect.qq.com/__trashed-2QQ互联提供有开发文档,帮助开发者实现QQ登录。
相关连接:http://wiki.connect.qq.com/准备工作_oauth2-01、客户端点击QQ登陆按钮,请求商城得到扫码登陆链接,
2、客户端打开扫码登陆链接,请求QQ互联返回客户端扫码登陆页面,
3、客户端扫码,请求QQ互联返回Authorization Code,
4、商城使用Authorization Code请求QQ互联得到access_token,
5、商城使用access_token请求QQ互联得到openid.
QQ登录成功后,我们需要将QQ用户和美多商场用户关联到一起,方便下次QQ登录时使用,所以我们选择使用MySQL数据库进行存储。
为了给项目中模型类补充数据创建时间和更新时间两个字段,我们需要定义模型类基类。 在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 # 说明是抽象模型类, 用于继承使用,数据库迁移时不会创建BaseModel的表创建一个新的应用oauth,用来实现QQ第三方认证登录。
# oauth
url(r'^oauth/', include('oauth.urls')),
在oauth/models.py中定义QQ身份(openid)与用户模型类User的关联关系
from django.db import models from meiduo_mall.utils.models import BaseModel # Create your models here.s class OAuthQQUser(BaseModel): """QQ登录用户数据""" user = models.ForeignKey('users.User', on_delete=models.CASCADE, verbose_name='用户') openid = models.CharField(max_length=64, verbose_name='openid', db_index=True) class Meta: db_table = 'tb_oauth_qq' verbose_name = 'QQ登录用户数据' verbose_name_plural = verbose_name$ python manage.py makemigrations $ python manage.py migrate
pip install QQLoginTool
from QQLoginTool.QQtool import OAuthQQ
login_url = oauth.get_qq_url()
access_token = oauth.get_access_token(code)
openid = oauth.get_open_id(access_token)
待处理业务逻辑
# 提取code请求参数
# 使用code向QQ服务器请求access_token
# 使用access_token向QQ服务器请求openid
# 使用openid查询该QQ用户是否在美多商城中绑定过用户
# 如果openid已绑定美多商城用户,直接生成JWT token,并返回
# 如果openid没绑定美多商城用户,创建用户并绑定到openid
1.请求方式
选项
方案
请求方法
GET
请求地址
/qq/authorization/
2.请求参数:查询参数
参数名
类型
是否必传
说明
next
string
否
用于记录QQ登录成功后进入的网址
3.响应结果:JSON
字段
说明
code
状态码
errmsg
错误信息
login_url
QQ登录扫码页面链接
4.后端逻辑实现
class QQAuthURLView(View): """提供QQ登录页面网址 https://graph.qq.com/oauth2.0/authorize?response_type=code&client_id=xxx&redirect_uri=xxx&state=xxx """ def get(self, request): # next表示从哪个页面进入到的登录页面,将来登录成功后,就自动回到那个页面 next = request.GET.get('next') # 获取QQ登录页面网址 oauth = OAuthQQ(client_id=settings.QQ_CLIENT_ID, client_secret=settings.QQ_CLIENT_SECRET, redirect_uri=settings.QQ_REDIRECT_URI, state=next) login_url = oauth.get_qq_url() return http.JsonResponse({'code': RETCODE.OK, 'errmsg': 'OK', 'login_url':login_url})5.QQ登录参数
QQ_CLIENT_ID = '101518219'
QQ_CLIENT_SECRET = '418d84ebdc7241efb79536886ae95224'
QQ_REDIRECT_URI = 'http://www.meiduo.site:8000/oauth_callback'
提示:
用户在QQ登录成功后,QQ会将用户重定向到我们配置的回调网址。在QQ重定向到回调网址时,会传给我们一个Authorization Code。我们需要拿到Authorization Code并完成OAuth2.0认证获取openid。在本项目中,我们申请QQ登录开发资质时配置的回调网址为:http://www.meiduo.site:8000/oauth_callback
QQ互联重定向的完整网址为:http://www.meiduo.site:8000/oauth_callback/?code=AE263F12675FA79185B54870D79730A7&state=/
class QQAuthUserView(View): """用户扫码登录的回调处理""" def get(self, request): """Oauth2.0认证""" # 接收Authorization Code code = request.GET.get('code') if not code: return http.HttpResponseForbidden('缺少code') pass
子应用路由
url(r'^oauth_callback/$', views.QQAuthUserView.as_view()),
编辑 /etc/hosts
sudo vim /etc/hosts
编辑 C:\Windows\System32\drivers\etc\hosts
使用openid查询该QQ用户是否在美多商城中绑定过用户。
try: oauth_user = OAuthQQUser.objects.get(openid=openid) except OAuthQQUser.DoesNotExist: # 如果openid没绑定美多商城用户 pass else: # 如果openid已绑定美多商城用户 pass如果openid已绑定美多商城用户,直接生成状态保持信息,登录成功,并重定向到首页。
try: oauth_user = OAuthQQUser.objects.get(openid=openid) except OAuthQQUser.DoesNotExist: # 如果openid没绑定美多商城用户 pass else: # 如果openid已绑定美多商城用户 # 实现状态保持 qq_user = oauth_user.user login(request, qq_user) # 响应结果 # 获取界面跳转来源 next = request.GET.get('state') response = redirect(next) # 登录时用户名写入到cookie,有效期15天 response.set_cookie('username', qq_user.username, max_age=3600 * 24 * 15) return responseoauth_callback.html中渲染access_token
<input v-model="access_token" type="hidden" name="access_token" value="{{ access_token }}">
安装:pip install itsdangerous
TimedJSONWebSignatureSerializer的使用 使用TimedJSONWebSignatureSerializer可以生成带有有效期的token from itsdangerous import TimedJSONWebSignatureSerializer as Serializer from django.conf import settings # serializer = Serializer(秘钥, 有效期秒) serializer = Serializer(settings.SECRET_KEY, 300) # serializer.dumps(数据), 返回bytes类型 token = serializer.dumps({'mobile': '18512345678'}) token = token.decode() # 检验token # 验证失败,会抛出itsdangerous.BadData异常 serializer = Serializer(settings.SECRET_KEY, 300) try: data = serializer.loads(token) except BadData: return None补充:openid签名处理
oauth.utils.py
def generate_eccess_token(openid): """ 签名openid :param openid: 用户的openid :return: access_token """ serializer = Serializer(settings.SECRET_KEY, expires_in=constants.ACCESS_TOKEN_EXPIRES) data = {'openid': openid} token = serializer.dumps(data) return token.decode()类似于用户注册的业务逻辑
当用户输入的手机号对应的用户已存在 直接将该已存在用户跟openid绑定 当用户输入的手机号对应的用户不存在 新建一个用户,并跟openid绑定 class QQAuthUserView(View): """用户扫码登录的回调处理""" def get(self, request): """Oauth2.0认证""" ...... def post(self, request): """美多商城用户绑定到openid""" # 接收参数 mobile = request.POST.get('mobile') pwd = request.POST.get('password') sms_code_client = request.POST.get('sms_code') access_token = request.POST.get('access_token') # 校验参数 # 判断参数是否齐全 if not all([mobile, pwd, sms_code_client]): return http.HttpResponseForbidden('缺少必传参数') # 判断手机号是否合法 if not re.match(r'^1[3-9]\d{9}$', mobile): return http.HttpResponseForbidden('请输入正确的手机号码') # 判断密码是否合格 if not re.match(r'^[0-9A-Za-z]{8,20}$', pwd): return http.HttpResponseForbidden('请输入8-20位的密码') # 判断短信验证码是否一致 redis_conn = get_redis_connection('verify_code') sms_code_server = redis_conn.get('sms_%s' % mobile) if sms_code_server is None: return render(request, 'oauth_callback.html', {'sms_code_errmsg':'无效的短信验证码'}) if sms_code_client != sms_code_server.decode(): return render(request, 'oauth_callback.html', {'sms_code_errmsg': '输入短信验证码有误'}) # 判断openid是否有效:错误提示放在sms_code_errmsg位置 openid = check_access_token(access_token) if not openid: return render(request, 'oauth_callback.html', {'openid_errmsg': '无效的openid'}) # 保存注册数据 try: user = User.objects.get(mobile=mobile) except User.DoesNotExist: # 用户不存在,新建用户 user = User.objects.create_user(username=mobile, password=pwd, mobile=mobile) else: # 如果用户存在,检查用户密码 if not user.check_password(pwd): return render(request, 'oauth_callback.html', {'account_errmsg': '用户名或密码错误'}) # 将用户绑定openid try: OAuthQQUser.objects.create(openid=openid, user=user) except DatabaseError: return render(request, 'oauth_callback.html', {'qq_login_errmsg': 'QQ登录失败'}) # 实现状态保持 login(request, user) # 响应绑定结果 next = request.GET.get('state') response = redirect(next) # 登录时用户名写入到cookie,有效期15天 response.set_cookie('username', user.username, max_age=3600 * 24 * 15) return response