接入QQ登陸前,網站需首先進行申請,得到對應的appid與appkey,以保證後續流程中可正確對網站與用戶進行驗證與受權。html
在開發的過程當中,發現獲取不到QQ號,只能獲取一個OpenID的東西。最後採起存儲這個OpenID並綁定對應帳號的方式。
因此須要建立對應的模型,即建立一個應用管理第三方登陸。前端
QQ登陸功能開發流程以下圖:python
打開QQ互聯,進入管理中心。註冊一下應用開發者,並添加網站應用,得到對應的appid與appkey。web
申請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
這個QQ按鈕是提供QQ登陸的入口。從騰訊提供的QQ按鈕下載放到你的登陸頁面便可。
不用看幫助文檔裏面的什麼前端代碼。這些用不上,只須要加一個固定的訪問連接,再重定向便可。
前端頁面此處的代碼以下:json
<div> <span>其餘登陸方式:</span> <a href="{% url 'qq_login' %}"> ![](/static/images/connect_qq.png) </a> </div>
qq_login連接在下面第3步建立web應用裏面設置。api
怎麼建立應用就不細說了,這是基本功。這裏我已經建立了一個名稱爲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是否存在數據庫中。若存在,則直接登陸對應的用戶便可;若不存在,則打開這個綁定郵箱頁面,綁定對應的用戶。
爲了管理好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域名文件解析):
# 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方式,把這兩個參數寫在連接上便可傳遞過去。
本地調試,先本地打開受權頁面受權,獲得一個回調地址。回調地址上有受權碼,以下圖:
上面提到若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中的展現>-----
-----<我是分割線,下面是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)
參考文章。。。。。。