接入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' %}">  </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,{{ 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)
参考文章。。。。。。