如何通过第三方QQ登录网站首页

2018-07-03 01:12:05来源:博客园 阅读 ()

新老客户大回馈,云服务器低至5折

QQ登录,就是我们所说的第三方登录,是指用户可以不在本项目中输入密码,而直接通过第三方的验证,成功登录本项目

若想实现QQ登录,需要成为QQ互联的开发者,审核通过才可实现。注册方法参考链接http://wiki.connect.qq.com/%E6%88%90%E4%B8%BA%E5%BC%80%E5%8F%91%E8%80%85

成为QQ互联开发者后,还需创建应用,即获取本项目对应与QQ互联的应用ID,最重要的是拿到APPID,创建应用的方法参考链接http://wiki.connect.qq.com/__trashed-2

申请时的网站域名即项目域名

网站回调域即用户通过扫码后需要跳转到的页面网址

QQ登录开发文档连接http://wiki.connect.qq.com/%E5%87%86%E5%A4%87%E5%B7%A5%E4%BD%9C_oauth2-0

此链接中的网站应用概述中 可以看到效果界面展示,然后点击进入网站开发流程,有5个下拉菜单,前两个是前端用的,后面三个是我们后端要用的

按照步骤获取Access_Token和Open_ID

下面是使用QQ登录的流程图

接下来就是写后台程序了

首先,创建QQ登录模型类,创建一个新的应用oauth,用来实现QQ第三方认证登录。总路由前缀 oauth/

终端进入到/meiduo_mall/apps目录下,使用命令python ../../manage.py startapp oauth 创建子应用oauth,记得创建完之后在settings文件中的INSTALLED_APPS中注册添加此应用,'oauth.apps.OauthConfig'

meiduo/meiduo_mall/utils/models.py文件中创建模型类基类,用于增加数据新建时间和更新时间。(为什么要在公共的util中增加models呢,因为在模型类基类中创建的create_time,update_time在后面的商品类等中都会用到,方便调用)

 

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/models.py中定义QQ身份(openid)与用户模型类User的关联关系

 

from django.db import models
from meiduo_mall.utils.models import BaseModel

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)#openid其实就是我们当年申请QQ时,QQ服务器为我们生成的那个唯一标识

  class Meta:
    db_table = 'tb_oauth_qq'
    verbose_name = 'QQ登录用户数据'
    verbose_name_plural = verbose_name

接下来,终端进入到包含manage.py文件的meiduo_mall目录下,输入迁移命令

进行数据库迁移

python manage.py makemigrations
python manage.py migrate

第一步:点击QQ登录标签,跳转到QQ登录页面
settings配置文件中添加:
# QQ登录参数
QQ_CLIENT_ID = '10147xxxx'
QQ_CLIENT_SECRET = 'c6ce949e04e12ecc909ae6a8b09b637c'
QQ_REDIRECT_URI = 'http://www.xxxx.xxxx:xxxx/oauth_callback.html'
QQ_STATE = '/'
 

新建oauth/utils.py文件,创建QQ登录辅助工具类

from urllib.parse import urlencode, parse_qs
from urllib.request import urlopen
from itsdangerous import TimedJSONWebSignatureSerializer as Serializer, BadData
from django.conf import settings
import json
import logging

from . import constants

logger = logging.getLogger('django')

class OAuthQQ(object):
 """ QQ登录的工具类 """
 """构造方法接收所有的工具方法需要用到的参数"""
def __init__(self, client_id=None, client_secret=None, redirect_uri=None, state=None): self.client_id = client_id or settings.QQ_CLIENT_ID #appid self.client_secret = client_secret or settings.QQ_CLIENT_SECRET #appkey self.redirect_uri = redirect_uri or settings.QQ_REDIRECT_URI self.state = state or settings.QQ_STATE # 用于保存登录成功后的跳转页面路径 def get_qq_login_url(self): """ 获取qq登录的网址 :return: url网址 """  params = { 'response_type': 'code',#此值固定为code,作用是告诉QQ服务器,此用户拿着QQ在扫码,是为了得到一个code,有code才能得到Access_Token
            'client_id': self.client_id,#申请QQ登录成功后,分配给应用的appid
            'redirect_uri': self.redirect_uri,#网站回调域的网址,必须是注册appid时填写的主域名下的地址,建议设置为网站首页或网站的用户中心。注意需要将url进行URLEncode
      'state': self.state, #就是next,# QQ登录成功后回去的地方
      'scope': 'get_user_info', #请求用户授权,手机正在扫码的用户,# 标识扫码最终为了获取QQ用户的信息(openid)
      
}
    
#拼接QQ扫码登录连接

    
login_url= 'https://graph.qq.com/oauth2.0/authorize?' + urlencode(params)
    return login_url

在oauth/views.py中实现视图

#  url(r'^qq/authorization/$', views.QQAuthURLView.as_view()), 
class QQAuthURLView(APIView): """ 获取QQ登录的url """ def get(self, request): """ 提供用于qq登录的url """
    #获取到next参数,实现将来从哪里进入的登录界面,QQ登录成功后,就回到哪里
next = request.query_params.get('next') oauth = OAuthQQ(state=next)
    
#生成QQ扫码登录连接(逻辑) login_url = oauth.get_qq_login_url() return Response({'login_url': login_url})

子应用中的urls中添加路由

from django.conf.urls import url
from . import views

urlpatterns=[
  url(r'^qq/authorization/$', views.QQAuthURLView.as_view()),
]

主业务逻辑的urls中添加子应用路由

urlpatterns = [
  #QQ登录
  url(r'^oauth/',include('oauth.urls')),

]

 

 urllib使用说明

在后端接口中,我们需要向QQ服务器发送请求,查询用户的QQ信息,Python提供了标准模块urllib可以帮助我们发送http请求。

  • urllib.parse.urlencode(query)

     

    将query字典转换为url路径中的查询字符串

  • urllib.parse.parse_qs(qs)

    将qs查询字符串格式数据转换为python的字典

  • urllib.request.urlopen(url, data=None)

    发送http请求,如果data为None,发送GET请求,如果data不为None,发送POST请求

    返回response响应对象,可以通过read()读取响应体数据,需要注意读取出的响应体数据为bytes类型

 第二步:用户扫码登录的回调处理

 

用户在QQ登录成功后,QQ会将用户重定向回我们配置的回调callback网址,即我们申请QQ登录开发资质时配置的回调地址;

 

第三步:绑定用户身份接口

 

业务逻辑:

 

  • 用户需要填写手机号、密码、图片验证码、短信验证码
  • 如果用户未在美多商城注册过,则会将手机号作为用户名为用户创建一个美多账户,并绑定用户
  • 如果用户已在美多商城注册过,则检验密码后直接绑定用户

 

注:以下代码合并二三步


新建oauth/serializers.py文件
from rest_framework import serializers
from .utils import OAuthQQ
from django_redis import get_redis_connection
from users.models import User
from .models import OAuthQQUser


class QQAuthUserSerializer(serializers.Serializer):
    """
    QQ登录创建用户序列化器
    """

    # 是外界的request.data传过来的注册时的请求体数据
    access_token = serializers.CharField(label='操作凭证')
    mobile = serializers.RegexField(label='手机号', regex=r'^1[3-9]\d{9}$')
    password = serializers.CharField(label='密码', max_length=20, min_length=8)
    sms_code = serializers.CharField(label='短信验证码')

    def validate(self, data):
        # 检验access_token
        access_token = data['access_token']
        # 获取身份凭证
        openid = OAuthQQ.check_save_user_token(access_token)
        if not openid:
            raise serializers.ValidationError('无效的access_token')

        # 将openid放在校验字典中,后面会使用
        data['openid'] = openid

        # 检验短信验证码
        mobile = data['mobile']
        sms_code = data['sms_code']
        redis_conn = get_redis_connection('verify_codes')
        real_sms_code = redis_conn.get('sms_%s' % mobile)
        if real_sms_code.decode() != sms_code:
            raise serializers.ValidationError('短信验证码错误')

        # 如果用户存在,检查用户密码
        try:
            user = User.objects.get(mobile=mobile)
        except User.DoesNotExist:
            pass
        else:
            password = data['password']
            if not user.check_password(password):
                raise serializers.ValidationError('密码错误')

            # 将认证后的user放进校验字典中,后续会使用
            data['user'] = user
        return data

    def create(self, validated_data):
        # 获取校验的用户
        user = validated_data.get('user')

        if not user:
            # 用户不存在,新建用户
            user = User.objects.create_user(
                username=validated_data['mobile'],
                password=validated_data['password'],
                mobile=validated_data['mobile'],
            )

        # 将用户绑定openid
        OAuthQQUser.objects.create(
            openid=validated_data['openid'],
            user=user
        )
        # 返回用户数据
        return user

 

新建oauth/exceptions.py文件
class QQAPIException(Exception):
    """自定义QQ异常"""
    pass

新建oauth/constants.py文件
# QQ登录保存用户数据的token有效期
SAVE_QQ_USER_TOKEN_EXPIRES=600

使用itsdangerous生成凭据access_token,itsdangerous模块的参考资料连接http://itsdangerous.readthedocs.io/en/latest/

需要安装itsdangerous   
pip install itsdangerous
使用TimedJSONWebSignatureSerializer可以生成带有有效期的token

在oauth/utils.py文件
OAuthQQ辅助类中添加方法:

from urllib.parse import urlencode,parse_qs
from urllib.request import urlopen

from .exceptions import QQAPIException

import json

from itsdangerous import TimedJSONWebSignatureSerializer as Serializer, BadData

 

 

from .exceptions import QQAPIException
from . import constants

 

import logging

 

# 日志记录器
logger = logging.getLogger('django')

def  get_access_token(self, code):

"""获取access_token"""


  # 准备url
  url = 'https://graph.qq.com/oauth2.0/token?'
  # 准备参数
  params = {
    'grant_type':'authorization_code',
    'client_id':self.client_id,
    'client_secret':self.client_secret,
    'code':code,
    'redirect_uri':self.redirect_uri
  }
  # 拼接地址
  url += urlencode(params)


  try:
    # 使用code向QQ服务器发送请求获取access_token
    response = urlopen(url)
    # 获取响应的二进制
    response_data = response.read()
    # 将response_data转成字符串
    # access_token=FE04************************CCE2&expires_in=7776000&refresh_token=88E4************************BE14
    response_str = response_data.decode()
    # 将response_str转成字典
    response_dict = parse_qs(response_str)
    # 提取access_token
    # response_dict.get('access_token') == [FE04************************CCE2]
    access_token = response_dict.get('access_token')[0]
  except  Exception as e:
    logger.error(e)
    raise QQAPIException('获取access_token失败')

  return access_token



def  get_openid(self, access_token):
  """
  使用access_token向QQ服务器请求openid
  :param access_token: 上一步获取的access_token
  :return: open_id
  """


  # 准备url
  url = 'https://graph.qq.com/oauth2.0/me?access_token=' + access_token


  # 美多向QQ服务器发送请求获取openid
  response_str = ''
  try:
    response = urlopen(url)
    response_str = response.read().decode()


    # 返回的数据 callback( {"client_id":"YOUR_APPID","openid":"YOUR_OPENID"} )\n;
    response_dict = json.loads(response_str[10:-4])
    # 获取openid
    openid = response_dict.get('openid')
  except Exception as e:
    # 如果有异常,QQ服务器返回 "code=xxx&msg=xxx"
    data = parse_qs(response_str)
    logger.error(e)
    raise QQAPIException('code=%s msg=%s' % (data.get('code'), data.get('msg')))

  return openid

 

@staticmethod
def generate_save_user_token(openid):
  """
  生成保存用户数据的token
  :param openid: 用户的openid
  :return: token
  """
  serializer = Serializer(settings.SECRET_KEY, expires_in=constants.SAVE_QQ_USER_TOKEN_EXPIRES)
  data = {'openid': openid}
  token = serializer.dumps(data)
  return token.decode()

 


@staticmethod
def check_save_user_token(token):
  """
  检验保存用户数据的token
  :param token: token
  :return: openid or None
  """
  serializer = Serializer(settings.SECRET_KEY, expires_in=constants.SAVE_QQ_USER_TOKEN_EXPIRES)
  try:
    data = serializer.loads(token)
  except BadData:
    return None
  else:
    return data.get('openid')

 

 

在oauth/views.py中实现视图:

from rest_framework.response import Response
from rest_framework import status
from rest_framework_jwt.views import api_settings
from rest_framework.generics import GenericAPIView

from .utils import OAuthQQ
from .exceptions import QQAPIException
from .models import OAuthQQUser
from . import serializers

 

# url(r'^qq/user/$', views.QQAuthUserView.as_view()),?
class QQAuthUserView(GenericAPIView):
  """用户扫码登录的回调处理"""

  # 指定序列化器
  serializer_class = serializers.QQAuthUserSerializer

  def get(self, request):
    # 提取code请求参数
    code = request.query_params.get('code')
    if not code:
    return Response({'message':'缺少code'}, status=status.HTTP_400_BAD_REQUEST)

    # 创建QQ登录的工具对象
    oauth = OAuthQQ()

    try:
      # 使用code向QQ服务器请求access_token
      access_token = oauth.get_access_token(code)

      # 使用access_token向QQ服务器请求openid
      openid = oauth.get_openid(access_token)
    except  QQAPIException:
      return Response({'message':'QQ服务异常'}, status=status.HTTP_503_SERVICE_UNAVAILABLE)

      # 使用openid查询该QQ用户是否在美多商城中绑定过用户
    try:
      oauth_user = OAuthQQUser.objects.get(openid=openid)
    except OAuthQQUser.DoesNotExist:
    # 如果openid没绑定美多商城用户,创建用户并绑定到openid
      access_token_openid = OAuthQQ.generate_save_user_token(openid)
      return Response({'access_token':access_token_openid})
    else:
      # 如果openid已绑定美多商城用户,直接生成JWT token,并返回
      jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
      jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER

      # 获取关联openid的user
      user = oauth_user.user

      payload = jwt_payload_handler(user)
      token = jwt_encode_handler(payload)

      # 向前端响应token, user_id,username
      return Response({
          'token':token,
          'user_id':user.id,
          'username':user.username
      })

  def post(self, request):
    """给openid绑定用户数据"""

    # 获取序列化器:注册的数据都在POST请求的请求体里面
    serializer = self.get_serializer(data=request.data)
    # 开启校验
    serializer.is_valid(raise_exception=True)
    # 保存校验的数据 : create会返回user
    user = serializer.save()

    # 生成JWT token 响应
    jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
    jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER

    payload = jwt_payload_handler(user)
    token = jwt_encode_handler(payload)

    # 向前端响应token, user_id,username
    return Response({
      'token': token,
      'user_id': user.id,
      'username': user.username
    })

 

在oauth/urls中 添加路由

urlpatterns=[
# 获取QQ扫码登录连接
url(r'^qq/authorization/$', views.QQAuthURLView.as_view()),
# 获取QQ登录用户信息(code,access_token, openid)
url(r'^qq/user/$', views.QQAuthUserView.as_view()),
]

 

最后再上一张绑定QQ身份的处理流程图供参考

 

 

 

 

标签:

版权申明:本站文章部分自网络,如有侵权,请联系:west999com@outlook.com
特别注意:本站所有转载文章言论不代表本站观点,本站所提供的摄影照片,插画,设计作品,如需使用,请与原作者联系,版权归原作者所有

上一篇:python之路----正则re(search,match,findall……)

下一篇:MongoDB 是由C++编写的?那么为何会在Python领域中风生水起呢?