Django增長QQ第三方登陸

準備工做_OAuth2.0

接入QQ登陸前,網站需首先進行申請,得到對應的appid與appkey,以保證後續流程中可正確對網站與用戶進行驗證與受權。
QQ-API調用步驟html

在開發的過程當中,發現獲取不到QQ號,只能獲取一個OpenID的東西。最後採起存儲這個OpenID並綁定對應帳號的方式。
因此須要建立對應的模型,即建立一個應用管理第三方登陸。前端

QQ登陸功能開發流程以下圖:
QQ第三方登陸python

第1步、QQ互聯註冊網站應用

打開QQ互聯,進入管理中心。註冊一下應用開發者,並添加網站應用,得到對應的appid與appkey。web

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/oau...
經過這種方式,能夠獲取受權碼,因此須要提供一個地址。這個地址先寫一個暫時沒有的地址,後面開發的時候,再給這個地址寫對應的響應方法。django

第2步、放置QQ按鈕

這個QQ按鈕是提供QQ登陸的入口。從騰訊提供的QQ按鈕下載放到你的登陸頁面便可。
QQ登陸按鈕
不用看幫助文檔裏面的什麼前端代碼。這些用不上,只須要加一個固定的訪問連接,再重定向便可。
前端頁面此處的代碼以下:json

<div>
    <span>其餘登陸方式:</span>
    <a href="{% url 'qq_login' %}">
        ![](/static/images/connect_qq.png)
    </a>
</div>

qq_login連接在下面第3步建立web應用裏面設置。api

第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號多是出於保護隱私的考慮。session

在總的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/oau...
咱們能夠獲取這個地址上面的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)



參考文章。。。。。。

相關文章
相關標籤/搜索