025、QQ互联

一、QQ登录开发文档

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

1. QQ互联开发者申请步骤

若想实现QQ登录,需要成为QQ互联的开发者,审核通过才可实现。

  • 相关连接:http://wiki.connect.qq.com/%E6%88%90%E4%B8%BA%E5%BC%80%E5%8F%91%E8%80%85

2. QQ互联应用申请步骤

成为QQ互联开发者后,还需创建应用,即获取本项目对应与QQ互联的应用ID。

  • 相关连接:http://wiki.connect.qq.com/__trashed-2

3. 网站对接QQ登录步骤

QQ互联提供有开发文档,帮助开发者实现QQ登录。

  • 相关连接:http://wiki.connect.qq.com/%E5%87%86%E5%A4%87%E5%B7%A5%E4%BD%9C_oauth2-0

4. QQ登录流程分析

1、客户端点击QQ登陆按钮,请求商城得到扫码登陆链接,

2、客户端打开扫码登陆链接,请求QQ互联返回客户端扫码登陆页面,

3、客户端扫码,请求QQ互联返回Authorization Code,

4、商城使用Authorization Code请求QQ互联得到access_token,

5、商城使用access_token请求QQ互联得到openid.

二、定义QQ登录模型类

QQ登录成功后,我们需要将QQ用户和美多商场用户关联到一起,方便下次QQ登录时使用,所以我们选择使用MySQL数据库进行存储。

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  # 说明是抽象模型类, 用于继承使用,数据库迁移时不会创建BaseModel的表

2. 定义QQ登录模型类

创建一个新的应用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

3. 迁移QQ登录模型类

$ python manage.py makemigrations $ python manage.py migrate

三、QQ登录工具QQLoginTool

1. QQLoginTool介绍

  • 该工具封装了QQ登录时对接QQ互联接口的请求操作。可用于快速实现QQ登录。

2. QQLoginTool安装

pip install QQLoginTool

3. QQLoginTool使用说明

1.导入

from QQLoginTool.QQtool import OAuthQQ

2.初始化OAuthQQ对象

oauth = OAuthQQ(client_id=settings.QQ_CLIENT_ID, client_secret=settings.QQ_CLIENT_SECRET, redirect_uri=settings.QQ_REDIRECT_URI, state=next)

3.获取QQ登录扫码页面,扫码后得到Authorization Code

login_url = oauth.get_qq_url()

4.通过Authorization Code获取Access Token

access_token = oauth.get_access_token(code)

5.通过Access Token获取OpenID

openid = oauth.get_open_id(access_token)

四、OAuth2.0认证获取openid

待处理业务逻辑

# 提取code请求参数

# 使用code向QQ服务器请求access_token

# 使用access_token向QQ服务器请求openid

# 使用openid查询该QQ用户是否在美多商城中绑定过用户

# 如果openid已绑定美多商城用户,直接生成JWT token,并返回

# 如果openid没绑定美多商城用户,创建用户并绑定到openid

1. 获取QQ登录扫码页面

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'

2. 接收Authorization Code

提示:

  • 用户在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=%2F

 

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()),

3. OAuth2.0认证获取openid

  1. 使用code向QQ服务器请求access_token
  2. 使用access_token向QQ服务器请求openid
class QQAuthUserView(View):
    """用户扫码登录的回调处理"""

    def get(self, request):
        """Oauth2.0认证"""
        # 提取code请求参数
        code = request.GET.get('code')
        if not code:
            return http.HttpResponseForbidden('缺少code')

        # 创建工具对象
        oauth = OAuthQQ(client_id=settings.QQ_CLIENT_ID, client_secret=settings.QQ_CLIENT_SECRET, redirect_uri=settings.QQ_REDIRECT_URI)

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

            # 使用access_token向QQ服务器请求openid
            openid = oauth.get_open_id(access_token)
        except Exception as e:
            logger.error(e)
            return http.HttpResponseServerError('OAuth2.0认证失败')
        pass

4. 本机绑定www.meiduo.site域名

编辑 /etc/hosts

sudo vim /etc/hosts

 

编辑 C:\Windows\System32\drivers\etc\hosts

五、openid是否绑定用户的处理

1. 判断openid是否绑定过用户

使用openid查询该QQ用户是否在美多商城中绑定过用户。

try:
    oauth_user = OAuthQQUser.objects.get(openid=openid)
except OAuthQQUser.DoesNotExist:
    # 如果openid没绑定美多商城用户
    pass
else:
    # 如果openid已绑定美多商城用户
    pass

2. openid已绑定用户的处理

如果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 response

3. openid未绑定用户的处理

  • 为了能够在后续的绑定用户操作中前端可以使用openid,在这里将openid签名后响应给前端。
  • openid属于用户的隐私信息,所以需要将openid签名处理,避免暴露。
try:
    oauth_user = OAuthQQUser.objects.get(openid=openid)
except OAuthQQUser.DoesNotExist:
    # 如果openid没绑定美多商城用户
    access_token = generate_eccess_token(openid)
    context = {'access_token': access_token}
    return render(request, 'oauth_callback.html', context)
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 response

oauth_callback.html中渲染access_token

4. 补充itsdangerous的使用

  • itsdangerous模块的参考资料链接 http://itsdangerous.readthedocs.io/en/latest/

安装: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绑定
  • 当用户输入的手机号对应的用户不存在
    • 新建一个用户,并跟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

 

你可能感兴趣的:(025、QQ互联)