Django之第三方平臺(tái)QQ授權(quán)登錄的實(shí)現(xiàn)
環(huán)境準(zhǔn)備
創(chuàng)建QQ互聯(lián)應(yīng)用
創(chuàng)建一個(gè)QQ互聯(lián)應(yīng)用,并獲取到App ID和App Key。
QQ互聯(lián)官網(wǎng):https://connect.qq.com/
開(kāi)發(fā)文檔:https://wiki.connect.qq.com/
創(chuàng)建應(yīng)用模塊
創(chuàng)建一個(gè)新的應(yīng)用oauth,用來(lái)實(shí)現(xiàn)QQ第三方認(rèn)證登錄的代碼編寫(xiě)。
python manage.py startapp oauth
在settings.py中注冊(cè)應(yīng)用
INSTALLED_APPS = [ # 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'apps.user', 'apps.oauth', ]
在settings.py同級(jí)目錄下的urls.py設(shè)置路由
url(r'^', include('apps.oauth.urls',namespace='oauth')),
定義QQ登錄模型類
在oauth/models.py中定義QQ身份(openid)與用戶模型類User的關(guān)聯(lián)關(guān)系
from django.db import models class OAuthQQUser(): """QQ登錄用戶""" user = models.ForeignKey('user.User', on_delete=models.CASCADE, verbose_name='用戶') openid = models.CharField(max_length=64, verbose_name='openid', db_index=True) create_time = models.DateTimeField(auto_now_add=True, verbose_name="創(chuàng)建時(shí)間") update_time = models.DateTimeField(auto_now=True, verbose_name="更新時(shí)間") class Meta: db_table = 'tb_oauth_qq' verbose_name = 'QQ登錄用戶' verbose_name_plural = verbose_name
執(zhí)行遷移
執(zhí)行遷移操作,生成QQ登錄模型類對(duì)應(yīng)的數(shù)據(jù)庫(kù)表
python manage.py makemigrations python manage.py migrate
QQLoginTool庫(kù)
騰訊QQ互聯(lián)平臺(tái)沒(méi)有Python SDK,但是可以使用第三方封裝好的SDK包,QQLoginTool是一個(gè)第三方庫(kù),封裝了對(duì)接QQ互聯(lián)的請(qǐng)求操作,可用于快速實(shí)現(xiàn)QQ登錄的一種工具包。
安裝QQLoginTool
pip install QQLoginTool
API使用說(shuō)明
導(dǎo)入
from QQLoginTool.QQtool import OAuthQQ
初始化OAuthQQ對(duì)象
oauth = OAuthQQ(client_id=settings.QQ_CLIENT_ID, client_secret=settings.QQ_CLIENT_SECRET, redirect_uri=settings.QQ_REDIRECT_URI, state=next)
獲取QQ登錄掃碼頁(yè)面,掃碼后得到Authorization Code
login_url = oauth.get_qq_url()
通過(guò)Authorization Code獲取Access Token
access_token = oauth.get_access_token(code)
通過(guò)Access Token獲取OpenID
openid = oauth.get_open_id(access_token)
在settings.py配置QQ登錄參數(shù)
QQ_CLIENT_ID = '1020343878' QQ_CLIENT_SECRET = 'Yu4123456LG0Yw53o' QQ_REDIRECT_URI = 'https://abc.com/oauth/callback'
QQ登錄掃碼頁(yè)面
from django.urls import re_path from . import views urlpatterns = [ re_path(r'^qq/login/$', views.QQAuthURLView.as_view()), ]
from QQLoginTool.QQtool import OAuthQQ from django import http from django.conf import settings from django.views import View from utils.response_code import RETCODE """ 提供QQ登錄頁(yè)面網(wǎng)址 https://graph.qq.com/oauth2.0/authorize?response_type=code&client_id=xxx&redirect_uri=xxx&state=xxx """ class QQAuthURLView(View): def get(self, request): # next: 從哪個(gè)頁(yè)面進(jìn)入到的登錄頁(yè)面,登錄成功后自動(dòng)回到那個(gè)頁(yè)面 next = request.GET.get('next') # 獲取QQ登錄頁(yè)面網(wǎng)址 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': 200 , 'msg': 'OK', 'login_url': login_url})
認(rèn)證獲取openid
1.用戶在QQ登錄成功后,QQ會(huì)將用戶重定向到配置的回調(diào)網(wǎng)址,同時(shí)會(huì)傳遞一個(gè)Authorization Code 2.拿到Authorization Code并完成OAuth2.0認(rèn)證獲取openid
注意:回調(diào)網(wǎng)址在申請(qǐng)QQ登錄開(kāi)發(fā)資質(zhì)時(shí)進(jìn)行配置
from django.urls import re_path from . import views urlpatterns = [ re_path(r'^oauth/callback/$', views.QQAuthUserView.as_view()), ]
使用code向QQ服務(wù)器請(qǐng)求,獲取access_token
使用access_token向QQ服務(wù)器請(qǐng)求獲取openid
"""用戶掃碼登錄的回調(diào)處理""" class QQAuthUserView(View): def get(self, request): """Oauth2.0認(rèn)證""" # 提取code請(qǐng)求參數(shù) code = request.GET.get('code') if not code: return http.HttpResponseBadRequest('缺少code') # 創(chuàng)建oauth 對(duì)象 oauth = OAuthQQ(client_id=settings.QQ_CLIENT_ID, client_secret=settings.QQ_CLIENT_SECRET, redirect_uri=settings.QQ_REDIRECT_URI) try: # 使用code向QQ服務(wù)器請(qǐng)求access_token access_token = oauth.get_access_token(code) # 使用access_token向QQ服務(wù)器請(qǐng)求openid openid = oauth.get_open_id(access_token) except Exception as e: logger.error(e) return http.HttpResponseServerError('OAuth2.0認(rèn)證失敗') pass
openid的判斷處理
openid是否綁定過(guò)用戶
判斷openid是否綁定過(guò)用戶,只需要使用openid查詢?cè)換Q用戶是否綁定過(guò)用戶即可。
oauth_user = OAuthQQUser.objects.get(openid=openid)
openid已綁定用戶
如果openid已綁定用戶,直接生成狀態(tài)保持信息,登錄成功,并重定向到首頁(yè)。
try: oauth_user = OAuthQQUser.objects.get(openid=openid) except OAuthQQUser.DoesNotExist: # 如果openid沒(méi)綁定用戶 pass else: # 如果openid已綁定用戶,實(shí)現(xiàn)狀態(tài)保持 qq_user = oauth_user.user login(request, qq_user) # 響應(yīng)結(jié)果 next = request.GET.get('state') response = redirect(next) # 登錄時(shí)用戶名寫(xiě)入到cookie,有效期15天 response.set_cookie('username', qq_user.username, max_age=3600 * 24 * 15) return response
openid未綁定用戶
openid屬于用戶隱私信息,在后續(xù)的綁定用戶操作中前端會(huì)使用openid,因此需要將openid簽名處理,避免暴露。
try: oauth_user = OAuthQQUser.objects.get(openid=openid) except OAuthQQUser.DoesNotExist: # 如果openid沒(méi)綁定用戶 generate_eccess_token:對(duì)openid簽名 access_token = generate_eccess_token(openid) context = {'access_token': access_token} return render(request, 'oauthCallback.html', context) else: qq_user = oauth_user.user login(request, qq_user) response = redirect(reverse('contents:index')) response.set_cookie('username', qq_user.username, max_age=3600 * 24 * 15) return response
oauthCallback.html中渲染access_token
<input type="hidden" name="access_token" value="{{ access_token }}">
openid簽名處理
簽名處理可以使用itsdangerous庫(kù),它是一個(gè)用于Python語(yǔ)言的庫(kù),提供了一些安全傳輸數(shù)據(jù)的工具類。其主要功能是在保證數(shù)據(jù)安全性的前提下,生成認(rèn)證令牌、時(shí)間限制的令牌和加密/解密數(shù)據(jù)信息等。
安裝itsdangerous
pip install itsdangerous
使用TimedJSONWebSignatureSerializer可以生成帶有有效期的token
from itsdangerous import TimedJSONWebSignatureSerializer as Serializer from django.conf import settings # serializer = Serializer(秘鑰, 有效期秒) serializer = Serializer(settings.SECRET_KEY, 300) # serializer.dumps(數(shù)據(jù)), 返回bytes類型 token = serializer.dumps({'mobile': '18381234567'}) token = token.decode() # 檢驗(yàn)token # 驗(yàn)證失敗,會(huì)拋出itsdangerous.BadData異常 serializer = Serializer(settings.SECRET_KEY, 300) try: data = serializer.loads(token) except BadData: return None
生成openid簽名與校驗(yàn)
from itsdangerous import BadData from itsdangerous import TimedJSONWebSignatureSerializer as Serializer from django.conf import settings def generate_eccess_token(openid): """ 對(duì)openid簽名 :param openid: 用戶openid """ serializer = Serializer(settings.SECRET_KEY, expires_in=3600) data = {'openid': openid} token = serializer.dumps(data) return token.decode() def check_access_token(access_token): """ 提取openid :param access_token: 簽名后的openid """ serializer = Serializer(settings.SECRET_KEY, expires_in=3600) try: data = serializer.loads(access_token) except BadData: return None else: return data.get('openid')
openid綁定用戶
openid綁定用戶的過(guò)程類似于用戶注冊(cè)的業(yè)務(wù)邏輯
class QQAuthUserView(View): """用戶掃碼登錄的回調(diào)處理""" def get(self, request): """Oauth2.0認(rèn)證""" ...... def post(self, request): """用戶綁定openid""" # 接收參數(shù) mobile = request.POST.get('mobile') password= request.POST.get('password') sms_code_client = request.POST.get('sms_code') access_token = request.POST.get('access_token') # 判斷參數(shù)是否齊全 if not all([mobile, password, sms_code_client]): return http.HttpResponseBadRequest('缺少必傳參數(shù)') # 判斷手機(jī)號(hào)是否合法 if not re.match(r'^1[3-9]\d{9}$', mobile): return http.HttpResponseBadRequest('請(qǐng)輸入正確的手機(jī)號(hào)碼') # 判斷密碼是否合格 if not re.match(r'^[0-9A-Za-z]{8,20}$', password): return http.HttpResponseBadRequest('請(qǐng)輸入8-20位的密碼') # 判斷短信驗(yàn)證碼是否一致 redis_conn = get_redis_connection('code') sms_code_server = redis_conn.get('sms_%s' % mobile) if sms_code_server is None: return render(request, 'oauthCallback.html', {'msg': '無(wú)效的短信驗(yàn)證碼'}) if sms_code_client != sms_code_server.decode(): return render(request, 'oauthCallback.html', {'msg': '輸入短信驗(yàn)證碼有誤'}) # 判斷openid是否有效 openid = check_access_token(access_token) if not openid: return render(request, 'oauthCallback.html', {'msg': '無(wú)效的openid'}) # 保存注冊(cè)數(shù)據(jù) try: user = User.objects.get(mobile=mobile) except User.DoesNotExist: # 用戶不存在,新建用戶 user = User.objects.create_user(username=mobile, password=password, mobile=mobile) else: # 如果用戶存在,檢查用戶密碼 if not user.check_password(password): return render(request, 'oauthCallback.html', {'msg': '用戶名或密碼錯(cuò)誤'}) # 將用戶綁定openid try: OAuthQQUser.objects.create(openid=openid, user=user) except DatabaseError: return render(request, 'oauthCallback.html', {'msg': 'QQ登錄失敗'}) # 實(shí)現(xiàn)狀態(tài)保持 login(request, user) # 響應(yīng)綁定結(jié)果 next = request.GET.get('state') response = redirect(next) # 登錄時(shí)用戶名寫(xiě)入到cookie,有效期15天 response.set_cookie('username', user.username, max_age=3600 * 24 * 15) return response
到此這篇關(guān)于Django之第三方平臺(tái)QQ授權(quán)登錄的實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)Django 第三方平臺(tái)QQ授權(quán)登錄內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
高質(zhì)量Python代碼編寫(xiě)的5個(gè)優(yōu)化技巧
這篇文章主要為大家詳細(xì)介紹了編寫(xiě)高質(zhì)量Python代碼的5個(gè)優(yōu)化技巧,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-11-11在Pandas中DataFrame數(shù)據(jù)合并,連接(concat,merge,join)的實(shí)例
今天小編就為大家分享一篇在Pandas中DataFrame數(shù)據(jù)合并,連接(concat,merge,join)的實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-01-01python爬蟲(chóng)遇到403錯(cuò)誤的問(wèn)題及解決
這篇文章主要介紹了python爬蟲(chóng)遇到403錯(cuò)誤的問(wèn)題及解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-02-02用python打印1~20的整數(shù)實(shí)例講解
在本篇內(nèi)容中小編給大家分享了關(guān)于python打印1~20的整數(shù)的具體步驟以及實(shí)例方法,需要的朋友們參考下。2019-07-07詳解小白之KMP算法及python實(shí)現(xiàn)
在看子串匹配問(wèn)題的時(shí)候,書(shū)上的關(guān)于KMP的算法的介紹總是理解不了??戳艘槐榇a總是很快的忘掉,后來(lái)決定好好分解一下KMP算法,算是給自己加深印象。感興趣的朋友跟隨小編一起看看吧2019-04-04Flask項(xiàng)目搭建配置項(xiàng)導(dǎo)入教程
這篇文章主要為大家介紹了Flask項(xiàng)目搭建配置項(xiàng)導(dǎo)入教程,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-11-11Python數(shù)據(jù)結(jié)構(gòu)與算法之圖的最短路徑(Dijkstra算法)完整實(shí)例
這篇文章主要介紹了Python數(shù)據(jù)結(jié)構(gòu)與算法之圖的最短路徑(Dijkstra算法),結(jié)合完整實(shí)例形式分析了Python圖的最短路徑算法相關(guān)原理與實(shí)現(xiàn)技巧,需要的朋友可以參考下2017-12-12Python PyQt5模塊實(shí)現(xiàn)窗口GUI界面代碼實(shí)例
這篇文章主要介紹了Python PyQt5模塊實(shí)現(xiàn)窗口GUI界面代碼實(shí)例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-05-05