準(zhǔn)備工作_OAuth2.0
接入QQ登錄前妖啥,網(wǎng)站需首先進(jìn)行申請(qǐng)陨晶,獲得對(duì)應(yīng)的appid與appkey芝硬,以保證后續(xù)流程中可正確對(duì)網(wǎng)站與用戶進(jìn)行驗(yàn)證與授權(quán)蚜点。
在開發(fā)的過程中,發(fā)現(xiàn)獲取不到QQ號(hào)拌阴,只能獲取一個(gè)OpenID的東西绍绘。最后采取存儲(chǔ)這個(gè)OpenID并綁定對(duì)應(yīng)賬號(hào)的方式。
所以需要?jiǎng)?chuàng)建對(duì)應(yīng)的模型皮官,即創(chuàng)建一個(gè)應(yīng)用管理第三方登錄脯倒。
QQ登錄功能開發(fā)流程如下圖:
第1步实辑、QQ互聯(lián)注冊(cè)網(wǎng)站應(yīng)用
打開QQ互聯(lián)捺氢,進(jìn)入管理中心。注冊(cè)一下應(yīng)用開發(fā)者剪撬,并添加網(wǎng)站應(yīng)用摄乒,獲得對(duì)應(yīng)的appid與appkey。
申請(qǐng)appid和appkey的用途
appid:應(yīng)用的唯一標(biāo)識(shí)。在OAuth2.0認(rèn)證過程中馍佑,appid的值即為oauth_consumer_key的值斋否。
appkey:appid對(duì)應(yīng)的密鑰,訪問用戶資源時(shí)用來驗(yàn)證應(yīng)用的合法性拭荤。在OAuth2.0認(rèn)證過程中茵臭,appkey的值即為oauth_consumer_secret的值。
理解回調(diào)地址需要了解一下OAuth協(xié)議舅世。
在你的網(wǎng)站頁面里面旦委,打開授權(quán)頁面(這個(gè)授權(quán)頁面不是回調(diào)地址)。在授權(quán)頁面里面雏亚,登錄QQ并確認(rèn)授權(quán)缨硝。
授權(quán)之后,會(huì)得到一個(gè)授權(quán)碼罢低〔楸纾回調(diào)地址就是用于接收這個(gè)授權(quán)碼。
授權(quán)碼以GET的方式返回网持,例如 http://www.junxi.site/web/oauth/qq/check/?code=xxxxxxxxxxxxx
通過這種方式宜岛,可以獲取授權(quán)碼,所以需要提供一個(gè)地址功舀。這個(gè)地址先寫一個(gè)暫時(shí)沒有的地址谬返,后面開發(fā)的時(shí)候,再給這個(gè)地址寫對(duì)應(yīng)的響應(yīng)方法日杈。
第2步遣铝、放置QQ按鈕
這個(gè)QQ按鈕是提供QQ登錄的入口。從騰訊提供的QQ按鈕下載放到你的登錄頁面即可莉擒。
不用看幫助文檔里面的什么前端代碼酿炸。這些用不上,只需要加一個(gè)固定的訪問鏈接涨冀,再重定向即可填硕。
前端頁面此處的代碼如下:
<div>
<span>其他登錄方式:</span>
<a href="{% url 'qq_login' %}">
![](/static/images/connect_qq.png)
</a>
</div>
qq_login鏈接在下面第3步創(chuàng)建web應(yīng)用里面設(shè)置。
第3步鹿鳖、創(chuàng)建web應(yīng)用
怎么創(chuàng)建應(yīng)用就不細(xì)說了扁眯,這是基本功。這里我已經(jīng)創(chuàng)建了一個(gè)名稱為web的django app應(yīng)用翅帜。
創(chuàng)建完成之后姻檀,打開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) # 關(guān)聯(lián)用戶信息表
qq_openid = models.CharField(max_length=64) # QQ的關(guān)聯(lián)OpenID
# def __str__(self):
# return self.user
該模型用于存儲(chǔ)QQ登錄返回的OpenID值涝滴。這個(gè)OpenID值是用QQ號(hào)一一對(duì)應(yīng)绣版。騰訊不給得到真實(shí)QQ號(hào)可能是出于保護(hù)隱私的考慮胶台。
在總的urls路由中,加入這個(gè)應(yīng)用路由杂抽。(總路由在和工程名一樣的文件夾中的urls.py文件诈唬。這種方式對(duì)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)),
]
路由控制根據(jù)自己的工程自己寫即可。
打開web應(yīng)用目錄下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铸磅,分別是打開授權(quán)頁面和回調(diào)地址。
bind_account是綁定用戶的頁面杭朱。
大致思路是授權(quán)之后愚屁,得到OpenID。判斷這個(gè)OpenID是否存在數(shù)據(jù)庫中痕檬。若存在霎槐,則直接登錄對(duì)應(yīng)的用戶即可;若不存在梦谜,則打開這個(gè)綁定郵箱頁面丘跌,綁定對(duì)應(yīng)的用戶。
第4步唁桩、開發(fā)OAuth登錄功能
為了管理好OAuth闭树,在web應(yīng)用的文件夾下創(chuàng)建oauth_client.py文件。把相關(guān)的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):
"""獲取授權(quán)頁面的網(wǎng)址"""
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
創(chuàng)建一個(gè)類报辱,需要申請(qǐng)QQ登錄的APP_ID、APP_KEY和回調(diào)地址单山。這些都是固定的碍现,我把這幾個(gè)常量放入到settings.py中。settings.py添加如下常量米奸,具體的值請(qǐng)?jiān)谀愕纳暾?qǐng)頁面查找(這里還需要提一下昼接,本地調(diào)試的方法。因?yàn)槭跈?quán)之后是調(diào)整到部署之后的網(wǎng)站上悴晰,而部署的網(wǎng)站還沒開發(fā)響應(yīng)的代碼慢睡,無法響應(yīng)對(duì)應(yīng)的地址。這里我是本地測(cè)試環(huán)境铡溪,強(qiáng)制綁定Hosts域名文件解析):
# OAuth設(shè)置
QQ_APP_ID = 'XXXXXX'
QQ_KEY = 'XXXXXX'
QQ_RECALL_URL = 'http://www.junxi.site/web/oauth/qq/check'
回到OAuthQQ類漂辐,現(xiàn)里面有個(gè)get_auth_url方法。該方法是獲取打開授權(quán)頁面的鏈接地址棕硫。(可參考官方幫助髓涯,寫得不夠清晰)
接著,在編輯web應(yīng)用的views.py文件饲帅,加入qq_login對(duì)應(yīng)的響應(yīng)方法:
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()
#重定向到授權(quán)頁面
return HttpResponseRedirect(url)
到這里為止复凳,就完成了點(diǎn)擊QQ登錄按鈕瘤泪,跳轉(zhuǎn)到授權(quán)頁面灶泵。
登錄授權(quán)之后育八,授權(quán)頁面會(huì)自動(dòng)跳轉(zhuǎn)到我們?cè)O(shè)置的回調(diào)地址。例如 http://www.junxi.site/web/oauth/qq/check?code=xxxxxxxxxxxxx
我們可以獲取這個(gè)地址上面的GET參數(shù)赦邻。先假設(shè)我們可以順利獲取到髓棋,繼續(xù)完善OAuthQQ類。拿到這個(gè)授權(quán)碼之后惶洲,需要用該碼獲取騰訊的access_token通行令牌按声。
打開oauth_client.py文件,在OAuthQQ類添加如下方法:
def get_access_token(self, code):
"""根據(jù)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} # 回調(diào)地址
url = 'https://graph.qq.com/oauth2.0/token?%s' % urllib.urlencode(params)
# 訪問該網(wǎng)址恬吕,獲取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签则,在服務(wù)器后臺(tái)訪問對(duì)應(yīng)的鏈接,獲取access_token铐料,并返回該值渐裂。因?yàn)槲液罄m(xù)不需要用access_token做其他動(dòng)作,直接一次性獲取QQ昵稱和OpenID钠惩。所以不用記錄這個(gè)通行令牌的有效期柒凉。
得到這個(gè)access_token之后,就可以做其他事了篓跛。首先需要獲取授權(quán)用戶的OpenID膝捞,因?yàn)轵v訊不允許獲取QQ號(hào)。只好退而求次愧沟,獲取并保存OpenID蔬咬。可參考官方文檔沐寺。
繼續(xù)給這個(gè)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基本信息的內(nèi)容格式都不一樣计盒。
再回頭編輯views.py,添加回調(diào)地址的處理方法:
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登錄芽丹,回調(diào)函數(shù)
"""登錄之后北启,會(huì)跳轉(zhuǎn)到這里。需要判斷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) # 稍微休息一下拔第,避免發(fā)送urlopen的10060錯(cuò)誤
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:
# 存在則獲取對(duì)應(yīng)的用戶咕村,并登錄
user = qq_open_id[0].user.username
print user
request.session['username'] = user
return HttpResponseRedirect('/web/')
else:
# 不存在,則跳轉(zhuǎn)到綁定用戶頁面
infos = oauth_qq.get_qq_info() # 獲取用戶信息
url = '%s?open_id=%s&nickname=%s' % (reverse('bind_account'), open_id, infos['nickname'])
return HttpResponseRedirect(url)
按照思路蚊俺,授權(quán)之后懈涛,調(diào)整到處理授權(quán)結(jié)果的頁面。獲取授權(quán)碼之后泳猬,用get_access_token方法得到access_token批钠。
再用access_token獲取OpenID宇植。坑出現(xiàn)了埋心,若不加time.sleep(0.05)休息一下的話指郁,會(huì)得到urlopen 10060錯(cuò)誤。
獲取到open_id之后拷呆,再判斷一下數(shù)據(jù)庫中是否存在闲坎。若存在,則已經(jīng)關(guān)聯(lián)對(duì)應(yīng)的用戶了茬斧,直接登錄該用戶腰懂。
若open_id不存在,則跳轉(zhuǎn)到綁定用戶的頁面项秉。該頁面需要知道open_id和QQ昵稱(為什么需要QQ昵稱绣溜,下一步會(huì)提到)。通過GET方式娄蔼,把這兩個(gè)參數(shù)寫在鏈接上即可傳遞過去怖喻。
本地調(diào)試,先本地打開授權(quán)頁面授權(quán)贷屎,得到一個(gè)回調(diào)地址罢防。回調(diào)地址上有授權(quán)碼唉侄,如下圖:
第5步咒吐、綁定用戶
上面提到若open_id在數(shù)據(jù)庫中不存在,則打開綁定用戶頁面属划。該頁面我設(shè)計(jì)成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 }}绽昼!您已登錄。請(qǐng)綁定用戶须蜗,完成QQ登錄硅确。</h2>
<div class="col-md-6">
<div class="login-mail">
<input type="text" placeholder="請(qǐng)輸入你的賬戶名" name="username" required="">
<i class="fa fa-user"></i>
</div>
<div class="login-mail">
<input type="text" placeholder="請(qǐng)輸入你的昵稱" name="nickname" required="">
<i class="fa fa-users"></i>
</div>
<div class="login-mail">
<input type="password" placeholder="請(qǐng)輸入密碼" name="password" required="">
<i class="fa fa-lock"></i>
</div>
<div class="login-mail">
<input type="password" placeholder="請(qǐng)?jiān)俅屋斎朊艽a" 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繼續(xù)編輯明肮,添加表單處理的對(duì)應(yīng)方法:
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 # 接收到前臺(tái)form表單傳過來的注冊(cè)賬戶信息
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 # 設(shè)置session
return response # 返回首頁
return render(request, 'qq-bind-account.html', locals())
訪問測(cè)試:
打開首頁
點(diǎn)擊QQ登錄
獲取授權(quán)并登錄
寫完代碼之后菱农,本地測(cè)試可以通過。最后再部署到服務(wù)器并在QQ互聯(lián)提交審核柿估。一般審核要1~2天左右循未。若審核不通過,又不明白審核說明秫舌,就直接找客服問問的妖。
-----<我是分割線绣檬,下面是項(xiàng)目在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()
# 重定向到授權(quán)頁面
return HttpResponseRedirect(url)
def qq_check(request): # 第三方QQ登錄,回調(diào)函數(shù)
"""登錄之后赋元,會(huì)跳轉(zhuǎn)到這里忘蟹。需要判斷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) # 稍微休息一下飒房,避免發(fā)送urlopen的10060錯(cuò)誤
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:
# 存在則獲取對(duì)應(yīng)的用戶搁凸,并登錄
user = qq_open_id[0].user.username
print user
request.session['username'] = user
return HttpResponseRedirect('/web/')
else:
# 不存在,則跳轉(zhuǎn)到綁定用戶頁面
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 # 接收到前臺(tái)form表單傳過來的注冊(cè)賬戶信息
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 # 設(shè)置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):
"""獲取授權(quán)頁面的網(wǎng)址"""
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):
"""根據(jù)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} # 回調(diào)地址
url = 'https://graph.qq.com/oauth2.0/token?%s' % urllib.urlencode(params)
# 訪問該網(wǎng)址狠毯,獲取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 />
參考文章
护糖。。嚼松。嫡良。。献酗。