Django增加QQ第三方登录

作者: 君惜丶 | 来源:发表于2017-07-12 14:18 被阅读2401次
    准备工作_OAuth2.0

    接入QQ登录前,网站需首先进行申请,获得对应的appid与appkey,以保证后续流程中可正确对网站与用户进行验证与授权。


    QQ-API调用步骤

    在开发的过程中,发现获取不到QQ号,只能获取一个OpenID的东西。最后采取存储这个OpenID并绑定对应账号的方式。
    所以需要创建对应的模型,即创建一个应用管理第三方登录。

    QQ登录功能开发流程如下图:


    QQ第三方登录

    第1步、QQ互联注册网站应用
    打开QQ互联,进入管理中心。注册一下应用开发者,并添加网站应用,获得对应的appid与appkey。

    APP ID和KEY

    申请appid和appkey的用途
    appid:应用的唯一标识。在OAuth2.0认证过程中,appid的值即为oauth_consumer_key的值。
    appkey:appid对应的密钥,访问用户资源时用来验证应用的合法性。在OAuth2.0认证过程中,appkey的值即为oauth_consumer_secret的值。

    应用申请 应用接口

    理解回调地址需要了解一下OAuth协议
    在你的网站页面里面,打开授权页面(这个授权页面不是回调地址)。在授权页面里面,登录QQ并确认授权。
    授权之后,会得到一个授权码。回调地址就是用于接收这个授权码。
    授权码以GET的方式返回,例如 http://www.junxi.site/web/oauth/qq/check/?code=xxxxxxxxxxxxx
    通过这种方式,可以获取授权码,所以需要提供一个地址。这个地址先写一个暂时没有的地址,后面开发的时候,再给这个地址写对应的响应方法。

    第2步、放置QQ按钮
    这个QQ按钮是提供QQ登录的入口。从腾讯提供的QQ按钮下载放到你的登录页面即可。

    QQ登录按钮
    不用看帮助文档里面的什么前端代码。这些用不上,只需要加一个固定的访问链接,再重定向即可。
    前端页面此处的代码如下:
    <div>
        <span>其他登录方式:</span>
        <a href="{% url 'qq_login' %}">
            ![](/static/images/connect_qq.png)
        </a>
    </div>
    

    qq_login链接在下面第3步创建web应用里面设置。

    第3步、创建web应用
    怎么创建应用就不细说了,这是基本功。这里我已经创建了一个名称为web的django app应用。
    创建完成之后,打开models.py文件,编写模型:

    #!/usr/bin/env python
    # _*_ coding:utf-8 _*_
    __author__ = 'junxi'
    
    import sys
    reload(sys)
    sys.setdefaultencoding('utf8')
    
    class OAuthQQ(models.Model):
        """QQ and User Bind"""
        user = models.ForeignKey(UserProfile)   # 关联用户信息表
        qq_openid = models.CharField(max_length=64)   # QQ的关联OpenID
        
        # def __str__(self):
        #    return self.user
    

    该模型用于存储QQ登录返回的OpenID值。这个OpenID值是用QQ号一一对应。腾讯不给得到真实QQ号可能是出于保护隐私的考虑。

    在总的urls路由中,加入这个应用路由。(总路由在和工程名一样的文件夹中的urls.py文件。这种方式对urls管理比较清晰)

    from django.conf.urls import url, include
    from django.contrib import admin
    import web.urls
    import web.views
    
    urlpatterns = [
        url(r'^admin/', admin.site.urls),
        url(r'^web/', include(web.urls)),
    ]
    

    路由控制根据自己的工程自己写即可。

    打开web应用目录下urls.py文件,先写一下需要哪些链接地址:

    from django.conf.urls import url
    from .views import *
    
    urlpatterns = [
        url(r'^oauth/qq/login/$', login, name='qq_login'),
        url(r'^oauth/qq/check/$', login, name='qq_check'),
        url(r'^oauth/bind/account/$', login, name='bind_account'),
    ]
    

    qq_login和qq_check,分别是打开授权页面和回调地址。
    bind_account是绑定用户的页面。
    大致思路是授权之后,得到OpenID。判断这个OpenID是否存在数据库中。若存在,则直接登录对应的用户即可;若不存在,则打开这个绑定邮箱页面,绑定对应的用户。

    第4步、开发OAuth登录功能
    为了管理好OAuth,在web应用的文件夹下创建oauth_client.py文件。把相关的OAuth操作方法集成在一起。编辑oauth_client.py文件:

    #!/usr/bin/env python
    # _*_ coding:utf-8 _*_
    __author__ = 'junxi'
    
    import json
    import urllib, urllib2, urlparse
    
    
    class OAuthQQ:
        def __init__(self, client_id, client_key, redirect_uri):
            self.client_id = client_id
            self.client_key = client_key
            self.redirect_uri = redirect_uri
    
        def get_auth_url(self):
            """获取授权页面的网址"""
            params = {'client_id': self.client_id,
                      'response_type': 'code',
                      'redirect_uri': self.redirect_uri,
                      'scope': 'get_user_info',
                      'state': 1}
            url = 'https://graph.qq.com/oauth2.0/authorize?%s' % urllib.urlencode(params)
            return url
    

    创建一个类,需要申请QQ登录的APP_ID、APP_KEY和回调地址。这些都是固定的,我把这几个常量放入到settings.py中。settings.py添加如下常量,具体的值请在你的申请页面查找(这里还需要提一下,本地调试的方法。因为授权之后是调整到部署之后的网站上,而部署的网站还没开发响应的代码,无法响应对应的地址。这里我是本地测试环境,强制绑定Hosts域名文件解析):

    修改Hosts文件
    # OAuth设置
    QQ_APP_ID = 'XXXXXX'
    QQ_KEY = 'XXXXXX'
    QQ_RECALL_URL = 'http://www.junxi.site/web/oauth/qq/check'
    

    回到OAuthQQ类,现里面有个get_auth_url方法。该方法是获取打开授权页面的链接地址。(可参考官方帮助,写得不够清晰)

    接着,在编辑web应用的views.py文件,加入qq_login对应的响应方法:

    from django.shortcuts import HttpResponseRedirect
    from django.conf import settings
    from oauth_client import OAuthQQ
     
    
    def qq_login(request):
        oauth_qq = OAuthQQ(settings.QQ_APP_ID, settings.QQ_KEY, settings.QQ_RECALL_URL)
     
        #获取 得到Authorization Code的地址
        url = oauth_qq.get_auth_url()
        #重定向到授权页面
        return HttpResponseRedirect(url)
    

    到这里为止,就完成了点击QQ登录按钮,跳转到授权页面。
    登录授权之后,授权页面会自动跳转到我们设置的回调地址。例如 http://www.junxi.site/web/oauth/qq/check?code=xxxxxxxxxxxxx
    我们可以获取这个地址上面的GET参数。先假设我们可以顺利获取到,继续完善OAuthQQ类。拿到这个授权码之后,需要用该码获取腾讯的access_token通行令牌。

    打开oauth_client.py文件,在OAuthQQ类添加如下方法:

        def get_access_token(self, code):
            """根据code获取access_token"""
            params = {'grant_type': 'authorization_code',
                      'client_id': self.client_id,
                      'client_secret': self.client_key,
                      'code': code,
                      'redirect_uri': self.redirect_uri}    # 回调地址
            url = 'https://graph.qq.com/oauth2.0/token?%s' % urllib.urlencode(params)
    
            # 访问该网址,获取access_token
            response = urllib2.urlopen(url).read()
            result = urlparse.parse_qs(response, True)
    
            access_token = str(result['access_token'][0])
            self.access_token = access_token
            return access_token
    

    该方法使用了urllib2,在服务器后台访问对应的链接,获取access_token,并返回该值。因为我后续不需要用access_token做其他动作,直接一次性获取QQ昵称和OpenID。所以不用记录这个通行令牌的有效期。

    得到这个access_token之后,就可以做其他事了。首先需要获取授权用户的OpenID,因为腾讯不允许获取QQ号。只好退而求次,获取并保存OpenID。可参考官方文档
    继续给这个OAuthQQ添加获取OpenID的方法和使用OpenID获取QQ基本信息的方法:

        def get_open_id(self):
            """获取QQ的OpenID"""
            params = {'access_token': self.access_token}
            url = 'https://graph.qq.com/oauth2.0/me?%s' % urllib.urlencode(params)
    
            response = urllib2.urlopen(url).read()
            v_str = str(response)[9:-3]  # 去掉callback的字符
            v_json = json.loads(v_str)
    
            openid = v_json['openid']
            self.openid = openid
            return openid
    
        def get_qq_info(self):
            """获取QQ用户的资料信息"""
            params = {'access_token': self.access_token,
                      'oauth_consumer_key': self.client_id,
                      'openid': self.openid}
            url = 'https://graph.qq.com/user/get_user_info?%s' % urllib.urlencode(params)
    
            response = urllib2.urlopen(url).read()
            return json.loads(response)
    

    腾讯返回OpenID和QQ基本信息的内容格式都不一样。

    再回头编辑views.py,添加回调地址的处理方法:

    from django.shortcuts import render, HttpResponseRedirect, HttpResponse, reverse # reverse url逆向解析
    from django.http import JsonResponse
    from . import models
    from .form import *
    import json
    import time
    from django.conf import settings
    from oauth_client import OAuthQQ
    
    def qq_check(request):  # 第三方QQ登录,回调函数
            """登录之后,会跳转到这里。需要判断code和state"""
            request_code = request.GET.get('code')
            oauth_qq = OAuthQQ(settings.QQ_APP_ID, settings.QQ_KEY, settings.QQ_RECALL_URL)
    
            # 获取access_token
            access_token = oauth_qq.get_access_token(request_code)
            time.sleep(0.05)  # 稍微休息一下,避免发送urlopen的10060错误
            open_id = oauth_qq.get_open_id()
            print open_id
    
            # 检查open_id是否存在
            qq_open_id = models.OAuthQQ.objects.filter(qq_openid=str(open_id))
            print qq_open_id
            if qq_open_id:
                # 存在则获取对应的用户,并登录
                user = qq_open_id[0].user.username
                print user
                request.session['username'] = user
                return HttpResponseRedirect('/web/')
            else:
                # 不存在,则跳转到绑定用户页面
                infos = oauth_qq.get_qq_info()  # 获取用户信息
                url = '%s?open_id=%s&nickname=%s' % (reverse('bind_account'), open_id, infos['nickname'])
                return HttpResponseRedirect(url)
    

    按照思路,授权之后,调整到处理授权结果的页面。获取授权码之后,用get_access_token方法得到access_token。
    再用access_token获取OpenID。坑出现了,若不加time.sleep(0.05)休息一下的话,会得到urlopen 10060错误。
    获取到open_id之后,再判断一下数据库中是否存在。若存在,则已经关联对应的用户了,直接登录该用户。
    若open_id不存在,则跳转到绑定用户的页面。该页面需要知道open_id和QQ昵称(为什么需要QQ昵称,下一步会提到)。通过GET方式,把这两个参数写在链接上即可传递过去。

    本地调试,先本地打开授权页面授权,得到一个回调地址。回调地址上有授权码,如下图:

    授权页面 回调地址 QQ提醒

    第5步、绑定用户
    上面提到若open_id在数据库中不存在,则打开绑定用户页面。该页面我设计成html表单,在templates下新建qq-bind-account.html文件。如下代码:

    {% extends 'base.html' %}
    
    {% block title %}
        <title>QQ和账户绑定</title>
    {% endblock %}
    
    {% block head-js %}
    {% endblock %}
    
    {% block nav %}
    {% endblock %}
    
    {% block content %}
    <form class="form-horizontal" enctype="multipart/form-data" action="" method="post">
        <div class="login">
            <h1><a href="{% url 'index' %}">Primumest</a></h1>
            <div class="login-bottom">
                <h2>HI,![](/static/images/connect_qq.png){{ nickname }}!您已登录。请绑定用户,完成QQ登录。</h2>
                <div class="col-md-6">
                    <div class="login-mail">
                        <input type="text" placeholder="请输入你的账户名" name="username" required="">
                        <i class="fa fa-user"></i>
                    </div>
                    <div class="login-mail">
                        <input type="text" placeholder="请输入你的昵称" name="nickname" required="">
                        <i class="fa fa-users"></i>
                    </div>
                    <div class="login-mail">
                        <input type="password" placeholder="请输入密码" name="password" required="">
                        <i class="fa fa-lock"></i>
                    </div>
                    <div class="login-mail">
                        <input type="password" placeholder="请再次输入密码" name="password" required="">
                        <i class="fa fa-lock"></i>
                    </div>
                </div>
                <div class="col-md-6 login-do">
                    <label class="hvr-shutter-in-horizontal login-sub">
                        <input type="submit" value="确定">
                    </label>
                </div>
                <div class="clearfix"></div>
            </div>
        </div>
    </form>
        <!---->
        <div class="copy-right">
            <p>© 2017 JunXi. All Rights Reserved</p>
        </div>
    {% endblock %}
    

    接着,在views.py继续编辑,添加表单处理的对应方法:

    def bind_account(request):  # 绑定账户
        open_id = request.GET.get('open_id')
        nickname = request.GET.get('nickname')
        if request.method == 'POST' and request.POST:
            data = request.POST # 接收到前台form表单传过来的注册账户信息
            user = models.UserProfile()
            username = data['username']
            password = data['password'].split(',')[0]
            user.username = username
            password = hash_sha256(password, username)
            user.password = password
            user.nickname = data['nickname']
            user.departments_id = 1
            user.save()
            oauthqq = models.OAuthQQ()
            oauthqq.qq_openid = open_id
            oauthqq.user_id = models.UserProfile.objects.get(username=username).id
            oauthqq.save()
            response = HttpResponseRedirect("/web/")
            request.session['username'] = username  # 设置session
            return response  # 返回首页
        return render(request, 'qq-bind-account.html', locals())
    

    访问测试:
    打开首页


    首页

    点击QQ登录


    获取授权页面 绑定用户界面

    获取授权并登录


    登录成功了

    写完代码之后,本地测试可以通过。最后再部署到服务器并在QQ互联提交审核。一般审核要1~2天左右。若审核不通过,又不明白审核说明,就直接找客服问问。

    -----<我是分割线,下面是项目在pycharm中的展示>-----

    项目展示1 项目展示2

    -----<我是分割线,下面是urls.py、view.py、oauth_client.py完整的代码>-----
    urls.py

    urlpatterns = [
        url(r'^oauth/qq/login', qq_login, name='qq_login'),
        url(r'^oauth/qq/check', qq_check, name='qq_check'),
        url(r'^oauth/bind/account', bind_account, name='bind_account'),
    ]
    

    views.py

    #!/usr/bin/env python
    # _*_ coding:utf-8 _*_
    __author__ = 'junxi'
    
    from django.shortcuts import render, HttpResponseRedirect, HttpResponse, reverse # reverse url逆向解析
    from django.http import JsonResponse
    from . import models
    from .form import *
    from script.salt_api import salt
    from script.web_ssh import webssh
    from django.contrib.auth.hashers import make_password, check_password
    # from django.forms.models import model_to_dict
    from django.core import serializers
    import datetime
    import json
    import hashlib
    import re
    import time
    import os
    from django.conf import settings
    from oauth_client import OAuthQQ
    
    
    def hash_sha256(password, username):  # sha256加密
        sha256 = hashlib.sha256()
        sha256.update((password + username).encode('utf-8'))
        sha256_password = sha256.hexdigest()
        return sha256_password
    
    
    def qq_login(request):  # 第三方QQ登录
        oauth_qq = OAuthQQ(settings.QQ_APP_ID, settings.QQ_KEY, settings.QQ_RECALL_URL)
    
        # 获取 得到Authorization Code的地址
        url = oauth_qq.get_auth_url()
        # 重定向到授权页面
        return HttpResponseRedirect(url)
    
    
    def qq_check(request):  # 第三方QQ登录,回调函数
            """登录之后,会跳转到这里。需要判断code和state"""
            request_code = request.GET.get('code')
            oauth_qq = OAuthQQ(settings.QQ_APP_ID, settings.QQ_KEY, settings.QQ_RECALL_URL)
    
            # 获取access_token
            access_token = oauth_qq.get_access_token(request_code)
            time.sleep(0.05)  # 稍微休息一下,避免发送urlopen的10060错误
            open_id = oauth_qq.get_open_id()
            print open_id
    
            # 检查open_id是否存在
            qq_open_id = models.OAuthQQ.objects.filter(qq_openid=str(open_id))
            print qq_open_id
            if qq_open_id:
                # 存在则获取对应的用户,并登录
                user = qq_open_id[0].user.username
                print user
                request.session['username'] = user
                return HttpResponseRedirect('/web/')
            else:
                # 不存在,则跳转到绑定用户页面
                infos = oauth_qq.get_qq_info()  # 获取用户信息
                url = '%s?open_id=%s&nickname=%s' % (reverse('bind_account'), open_id, infos['nickname'])
                return HttpResponseRedirect(url)
    
    
    def bind_account(request):  # 绑定账户
        open_id = request.GET.get('open_id')
        nickname = request.GET.get('nickname')
        if request.method == 'POST' and request.POST:
            data = request.POST # 接收到前台form表单传过来的注册账户信息
            user = models.UserProfile()
            username = data['username']
            password = data['password'].split(',')[0]
            user.username = username
            password = hash_sha256(password, username)
            user.password = password
            user.nickname = data['nickname']
            user.departments_id = 1
            user.save()
            oauthqq = models.OAuthQQ()
            oauthqq.qq_openid = open_id
            oauthqq.user_id = models.UserProfile.objects.get(username=username).id
            oauthqq.save()
            response = HttpResponseRedirect("/web/")
            request.session['username'] = username  # 设置session
            return response  # 返回首页
        return render(request, 'qq-bind-account.html', locals())
    

    oauth_client.py

    #!/usr/bin/env python
    # _*_ coding:utf-8 _*_
    __author__ = 'junxi'
    
    import json
    import urllib, urllib2, urlparse
    
    
    class OAuthQQ:
        def __init__(self, client_id, client_key, redirect_uri):
            self.client_id = client_id
            self.client_key = client_key
            self.redirect_uri = redirect_uri
    
        def get_auth_url(self):
            """获取授权页面的网址"""
            params = {'client_id': self.client_id,
                      'response_type': 'code',
                      'redirect_uri': self.redirect_uri,
                      'scope': 'get_user_info',
                      'state': 1}
            url = 'https://graph.qq.com/oauth2.0/authorize?%s' % urllib.urlencode(params)
            return url
    
        def get_access_token(self, code):
            """根据code获取access_token"""
            params = {'grant_type': 'authorization_code',
                      'client_id': self.client_id,
                      'client_secret': self.client_key,
                      'code': code,
                      'redirect_uri': self.redirect_uri}    # 回调地址
            url = 'https://graph.qq.com/oauth2.0/token?%s' % urllib.urlencode(params)
    
            # 访问该网址,获取access_token
            response = urllib2.urlopen(url).read()
            result = urlparse.parse_qs(response, True)
    
            access_token = str(result['access_token'][0])
            self.access_token = access_token
            return access_token
    
        def get_open_id(self):
            """获取QQ的OpenID"""
            params = {'access_token': self.access_token}
            url = 'https://graph.qq.com/oauth2.0/me?%s' % urllib.urlencode(params)
    
            response = urllib2.urlopen(url).read()
            v_str = str(response)[9:-3]  # 去掉callback的字符
            v_json = json.loads(v_str)
    
            openid = v_json['openid']
            self.openid = openid
            return openid
    
        def get_qq_info(self):
            """获取QQ用户的资料信息"""
            params = {'access_token': self.access_token,
                      'oauth_consumer_key': self.client_id,
                      'openid': self.openid}
            url = 'https://graph.qq.com/user/get_user_info?%s' % urllib.urlencode(params)
    
            response = urllib2.urlopen(url).read()
            return json.loads(response)
    

    <br />
    参考文章
    。。。。。。

    相关文章

      网友评论

      • fa4715aece23:很不错
      • 郭效杨:你好,我想问问这样我们先从qq互联获得appid和secret,那么第三方登录会自动获得这些数据,那如果我用其他的qq号登陆呢?那么一开始在settings.py设置的appid和secret不就不需要了吗?按照博主教程是否只是关联到settings.py的那个qq号,其他qq号能否直接登陆?
      • 魏什么学Python:学习了,非常非常非常详细!!!!
      • 假面_a031:按照你上面的代码,访问出现redirect uri is illegal(100010) ,能帮忙解决一下吗
      • 22b18b7a3586:infos = oauth_qq.get_qq_info() # 获取用户信息
        url = '%s?open_id=%s&nickname=%s' % (reverse('bind_account'), open_id, infos['nickname'])
        return HttpResponseRedirect(url)

        这个infos['nickname'] 在QQ登陆信息里面是不存在的,那这个是在哪里存在的呢。
        22b18b7a3586:@君惜丶 get_qq_info 没有返回的nickname 字段呢?
        君惜丶:请求https://graph.qq.com/user/get_user_info这个地址返回的QQ用户信息。 你看下OAuthQQ类的get_qq_info方法。
      • 3337927622a3:能写详细点吗,一下oauth应用,一下web应用,views到底是在哪个应用下的
        an鑫_wolfxin2010:步骤已经很详细,博主辛苦了
        君惜丶:web应用,文章结尾我加上项目目录展示了。

      本文标题:Django增加QQ第三方登录

      本文链接:https://www.haomeiwen.com/subject/ubhucxtx.html