oAuth2.0 第三方登录(Github+QQ+新浪微博)django开发

oAuth2.0开发流程

OAuth(开放授权 Open Authorization)是一个开放标准,允许用户授权第三方网站访问他们存储在另外的服务提供者上的信息,而不需要将用户名和密码提供给第三方网站或分享他们数据的所有内容。
处理流程包括:

  • 获取授权Code
  • 利用Code请求Access Token (令牌)
  • 利用令牌请求用户的Openid及其他用户用户信息 (每个用户拥有唯一的Openid,因此可以通过Openid标识用户)
    这里写图片描述
    回调地址就是你接受code的url,要在第三方登录平台上填写,这样才能把code传递给你的网站,我这里填写的是http://127.0.0.1:8000/ 用于测试。

获取应用Id和秘钥

开发之前,需要前往第三方登录的开发者平台QQ新浪微博Github,注册账号并填写信息申请接入,成功后会给你一个ID和秘钥,以后你就通过该ID和秘钥来获取令牌,从而实现第三方登录。申请ID和秘钥时Github不需要审核,很简单,而QQ和新浪微博需要审核,稍微麻烦一点。

settings设置

#Oauth

#github
GITHUB_APP_ID = '************'

GITHUB_KEY = '************'

GITHUB_CALLBACK_URL = 'http://127.0.0.1:8000/oauth/github_check'  #填写你的回调地址

#QQ
QQ_APP_ID = '************'

QQ_KEY = '*******************8'

QQ_CALLBACK_URL = 'http://127.0.0.1:8000/oauth/qq_check'    #填写你的回调地址

#新浪微博
WEIBO_APP_ID = '************'

WEIBO_KEY = '*****************'

WEIBO_CALLBACK_URL = 'http://127.0.0.1:8000/oauth/weibo_check'    #填写你的回调地址

模型

from django.db import models
from users.models import User
 
#用户登录的类型
type=(
	('1','github'),
	('2','qq'),
	('3','weibo')
)

class OAuth_ex(models.Model):
	user = models.ForeignKey(User)           #User为本网站的用户模型,每个第三方账号都要绑定本站账号
	openid = models.CharField(max_length=100,default='')
	type = models.CharField(max_length=1,choices=type)

url配置

from django.conf.urls import url
from . import views

app_name='oauth'

urlpatterns=[
	url(r'github_login',views.git_login,name='github_login'),
	url(r'github_check',views.git_check,name='github_check'),
	url(r'qq_login',views.qq_login,name='qq_login'),
	url(r'qq_check',views.qq_check,name='qq_check'),
	url(r'weibo_login',views.weibo_login,name='weibo_login'),
	url(r'weibo_check',views.weibo_check,name='weibo_check'),
	url(r'bind_email',views.bind_email,name='bind_email'),   #通过邮箱将第三方账户绑定到本站账号
]

辅助类

因为三种登录方式都遵循相似的处理流程,因此我们新建一个文件,将登录所需要的方法都封装到对应的类中,减少代码冗余,也方便理解。

import json
import urllib
import re

class OAuth_Base(object):    #基类,将相同的方法写入到此类中
	def __init__(self,client_id,client_key,redirect_url):   #初始化,载入对应的应用id、秘钥和回调地址
		self.client_id = client_id
		self.client_key = client_key
		self.redirect_url = redirect_url
	
	def _get(self,url,data):      #get方法
		request_url = '%s?%s' % (url,urllib.parse.urlencode(data))
		response = urllib.request.urlopen(request_url)
		return response.read()
		
	def _post(self,url,data):    #post方法
		request = urllib.request.Request(url,data = urllib.parse.urlencode(data).encode(encoding='UTF8'))     #1
		response = urllib.request.urlopen(request)
		return response.read()

#下面的方法,不同的登录平台会有细微差别,需要继承基类后重写方法

	def get_auth_url(self):   #获取code
		pass

	def get_access_token(self,code):   #获取access token
		pass
		
	def get_open_id(self):    #获取openid
		pass
	
	def get_user_info(self):   #获取用户信息
		pass
		
	def get_email(self):   #获取用户邮箱
		pass
		
#Github类
class OAuth_GITHUB(OAuth_Base):
	def get_auth_url(self):
		params = {
			'client_id':self.client_id,
			'response_type':'code',
			'redirect_uri':self.redirect_url,
			'scope':'user:email',
			'state':1
		}
		url = 'https://github.com/login/oauth/authorize?%s' % urllib.parse.urlencode(params)
		return url

	def get_access_token(self,code):
		params = {
			'grant_type':'authorization_code',
			'client_id':self.client_id,
			'client_secret':self.client_key,
			'code':code,
			'redirect_url':self.redirect_url
		}
		response = self._post('https://github.com/login/oauth/access_token',params)  #此处为post方法
		result = urllib.parse.parse_qs(response,True)
		self.access_token = result[b'access_token'][0]    
		return self.access_token
		
#github不需要获取openid,因此不需要get_open_id()方法

	def get_user_info(self):
		params ={'access_token':self.access_token}
		response = self._get('https://api.github.com/user',params)
		result = json.loads(response.decode('utf-8'))    
		self.openid = result.get('id','')
		return result
		
	def get_email(self):
		params ={'access_token':self.access_token}
		response = self._get('https://api.github.com/user/emails',params)
		result = json.loads(response.decode('utf-8'))
		return result[0]['email']

#QQ类	
class OAuth_QQ(OAuth_Base):
	def get_auth_url(self):
		params = {
			'client_id':self.client_id,
			'response_type':'code',
			'redirect_uri':self.redirect_url,
			'scope':'get_user_info',
			'state':1
		}
		url = 'https://graph.qq.com/oauth2.0/authorize?%s' % urllib.parse.urlencode(params)
		return url

	def get_access_token(self,code):
		params = {
			'grant_type':'authorization_code',
			'client_id':self.client_id,
			'client_secret':self.client_key,
			'code':code,
			'redirect_uri':self.redirect_url
		}
		response = self._get('https://graph.qq.com/oauth2.0/token',params)
		result = urllib.parse.parse_qs(response,True)
		self.access_token = result[b'access_token'][0]		
		return self.access_token
		
	def get_open_id(self):
		params ={'access_token':self.access_token}
		response = self._get('https://graph.qq.com/oauth2.0/me',params)
		response = re.split("[()]",response.decode('utf-8'))[1]   #将回应中的callback前缀去掉
		result = json.loads(response)	
		self.openid = result.get('openid','')
		return self.openid
	
	def get_user_info(self):
		params ={
			'access_token':self.access_token,
			'openid':self.openid,	
			'oauth_consumer_key':self.client_id,
		}
		response = self._get('https://graph.qq.com/user/get_user_info',params)
		result = json.loads(response.decode('utf-8'))    
		return result

#因为QQ没有开放获取qq邮箱的接口,因此不需要重写get_email()

#微博类
class OAuth_WEIBO(OAuth_Base):
	def get_auth_url(self):
		params = {
			'client_id':self.client_id,
			'response_type':'code',
			'redirect_uri':self.redirect_url,
			'scope':'email',
			'state':1
		}
		url = 'https://api.weibo.com/oauth2/authorize?%s' % urllib.parse.urlencode(params)
		return url
		
	def get_access_token(self,code):
		params = {
			'grant_type':'authorization_code',
			'client_id':self.client_id,
			'client_secret':self.client_key,
			'code':code,
			'redirect_uri':self.redirect_url
		}
		response = self._post('https://api.weibo.com/oauth2/access_token',params)
		result = json.loads(response.decode('utf-8'))
		self.access_token = result["access_token"]
		self.openid = result["uid"]
		return self.access_token
	
	def get_open_id(self):    #新浪的openid在之前get_access_token()方法中已经获得
		return self.openid

	def get_user_info(self):
		params ={
			'access_token':self.access_token,
			'uid':self.openid,	
		}
		response = self._get('https://api.weibo.com/2/users/show.json',params)
		result = json.loads(response.decode('utf-8'))   
		return result
		
	def get_email(self):
		params ={'access_token':self.access_token}
		response = self._get('https://api.weibo.com/2/account/profile/email.json',params)
		result = json.loads(response.decode('utf-8'))
		return result[0]['email']

视图函数

from django.shortcuts import render,render_to_response
from django.http import HttpResponseRedirect
from django.conf import settings
from .oauth_client import OAuth_GITHUB,OAuth_QQ,OAuth_WEIBO
from .models import OAuth_ex
from django.contrib.auth import login as auth_login
from users.models import User
from django.core.urlresolvers import reverse
from .forms import BindEmail

import time,uuid
	
def git_login(request):   #获取code
	oauth_git = OAuth_GITHUB(settings.GITHUB_APP_ID,settings.GITHUB_KEY,settings.GITHUB_CALLBACK_URL)
	url = oauth_git.get_auth_url()
	return HttpResponseRedirect(url)

def git_check(request):
	type='1'
	request_code = request.GET.get('code')
	oauth_git = OAuth_GITHUB(settings.GITHUB_APP_ID,settings.GITHUB_KEY,settings.GITHUB_CALLBACK_URL)
	try:
		access_token = oauth_git.get_access_token(request_code)   #获取access token
		time.sleep(0.1)    #此处需要休息一下,避免发送urlopen的10060错误
	except:  #获取令牌失败,反馈失败信息
		data={}
		data['goto_url'] = '/'
		data['goto_time'] = 10000
		data['goto_page'] = True
		data['message_title'] = '登录失败'
		data['message'] = '获取授权失败,请确认是否允许授权,并重试。若问题无法解决,请联系网站管理人员'
		return render_to_response('oauth/response.html',data)
	infos = oauth_git.get_user_info()   #获取用户信息
	nickname = infos.get('login','')
	image_url = infos.get('avatar_url','')
	open_id = str(oauth_git.openid)
	signature = infos.get('bio','')
	if not signature:
		signature = "无个性签名"
	sex = '1'
	githubs = OAuth_ex.objects.filter(openid=open_id,type=type)   #查询是否该第三方账户已绑定本网站账号
	if githubs:   #若已绑定,直接登录
		auth_login(request,githubs[0].user,backend='django.contrib.auth.backends.ModelBackend')
		return HttpResponseRedirect('/')
	else:   #否则尝试获取用户邮箱用于绑定账号
		try:
			email = oauth_git.get_email()
		except:   #若获取失败,则跳转到绑定用户界面,让用户手动输入邮箱
			url = "%s?nickname=%s&openid=%s&type=%s&signature=%s&image_url=%s&sex=%s" % (reverse('oauth:bind_email'),nickname,open_id,type,signature,image_url,sex)
			return HttpResponseRedirect(url)
	users = User.objects.filter(email=email)   #若获取到邮箱,则查询是否存在本站用户
	if users:   #若存在,则直接绑定
		user = users[0]
	else:   #若不存在,则新建本站用户
		while User.objects.filter(username=nickname):   #防止用户名重复
			nickname = nickname + '*'
		user = User(username=nickname,email=email,sex=sex,signature=signature)
		pwd = str(uuid.uuid1())   #随机设置用户密码
		user.set_password(pwd)
		user.is_active = True
		user.download_image(image_url,nickname)   #下载用户头像图片
		user.save()
	oauth_ex = OAuth_ex(user = user,openid = open_id,type=type)
	oauth_ex.save()    #保存后登陆
	auth_login(request,user,backend='django.contrib.auth.backends.ModelBackend')
	data={}    #反馈登陆结果
	data['goto_url'] = '/'
	data['goto_time'] = 10000
	data['goto_page'] = True
	data['message_title'] = '绑定用户成功'
	data['message'] = u'绑定成功!您的用户名为:<b>%s</b>。您现在可以同时使用本站账号和此第三方账号登录本站了!' % nickname
	return render_to_response('oauth/response.html',data)
	
def qq_login(request):
	oauth_qq = OAuth_QQ(settings.QQ_APP_ID,settings.QQ_KEY,settings.QQ_CALLBACK_URL)
	url = oauth_qq.get_auth_url()
	return HttpResponseRedirect(url)
	
def qq_check(request):
	type = 2
	code = request.GET.get('code','')
	oauth_qq = OAuth_QQ(settings.QQ_APP_ID,settings.QQ_KEY,settings.QQ_CALLBACK_URL)
	try:
		access_token = oauth_qq.get_access_token(code)
		time.sleep(0.1)
	except:
		data={}
		data['goto_url'] = '/'
		data['goto_time'] = 10000
		data['goto_page'] = True
		data['message_title'] = '登录失败'
		data['message'] = '获取授权失败,请确认是否允许授权,并重试。若问题无法解决,请联系网站管理人员'
		return render_to_response('oauth/response.html',data)
	openid = oauth_qq.get_open_id()
	qqs = OAuth_ex.objects.filter(openid=openid,type=type)
	if qqs:
		auth_login(request,qqs[0].user,backend='django.contrib.auth.backends.ModelBackend')
		return HttpResponseRedirect('/')
	else:
		infos = oauth_qq.get_user_info()
		nickname = infos.get('nickname','')
		image_url = infos.get('figureurl_qq_1','')
		sex = '1' if infos.get('gender','') == '男' else '2'
		signature = '无个性签名'
		url = "%s?nickname=%s&openid=%s&type=%s&signature=%s&image_url=%s&sex=%s" % (reverse('oauth:bind_email'),nickname,openid,type,signature,image_url,sex)
		return HttpResponseRedirect(url)
		
def weibo_login(request):
	oauth_weibo = OAuth_WEIBO(settings.WEIBO_APP_ID,settings.WEIBO_KEY,settings.WEIBO_CALLBACK_URL)
	url = oauth_weibo.get_auth_url()
	return HttpResponseRedirect(url)

def weibo_check(request):
	type = 3
	code = request.GET.get('code','')
	oauth_weibo = OAuth_WEIBO(settings.WEIBO_APP_ID,settings.WEIBO_KEY,settings.WEIBO_CALLBACK_URL)
	try:
		oauth_weibo.get_access_token(code)
		time.sleep(0.1)
	except:
		data={}
		data['goto_url'] = '/'
		data['goto_time'] = 10000
		data['goto_page'] = True
		data['message_title'] = '登录失败'
		data['message'] = '获取授权失败,请确认是否允许授权,并重试。若问题无法解决,请联系网站管理人员'
		return render_to_response('oauth/response.html',data)
	openid = oauth_weibo.get_open_id()
	weibos = OAuth_ex.objects.filter(openid=openid,type=type)
	if weibos:
		auth_login(request,weibos[0].user,backend='django.contrib.auth.backends.ModelBackend')
		return HttpResponseRedirect('/')
	else:
		try:
			email = oauth_weibo.get_email()
		except:
			infos = oauth_weibo.get_user_info()
			nickname = infos.get('screen_name','')
			image_url = infos.get('avatar_large','')
			signature = infos.get('description','')
			if not signature:
				signature = "无个性签名"
			print("signature="+signature)
			sex = '2' if infos.get('gender','') == 'f' else '1'
			url = "%s?nickname=%s&openid=%s&type=%s&signature=%s&image_url=%s&sex=%s" % (reverse('oauth:bind_email'),nickname,openid,type,signature,image_url,sex)
			return HttpResponseRedirect(url)
	users = User.objects.filter(email=email)
	if users:
		user = users[0]
	else:
		while User.objects.filter(username=nickname):
			nickname = nickname + '*'
		user = User(username=nickname,email=email,sex=sex,signature=signature)
		pwd = str(uuid.uuid1())
		user.set_password(pwd)
		user.is_active = True
		user.download_image(image_url,nickname)
		user.save()
	oauth_ex = OAuth_ex(user = user,openid = openid,type=type)
	oauth_ex.save()
	auth_login(request,user,backend='django.contrib.auth.backends.ModelBackend')
	data={}
	data['goto_url'] = '/'
	data['goto_time'] = 10000
	data['goto_page'] = True
	data['message_title'] = '绑定用户成功'
	data['message'] = u'绑定成功!您的用户名为:<b>%s</b>。您现在可以同时使用本站账号和此第三方账号登录本站了!' % nickname
	return render_to_response('oauth/response.html',data)	
	
	
	
def bind_email(request):   #使用户手动填写邮箱,绑定本站账号,因此需要一个BindEmail表单
	sex = request.GET.get('sex',request.POST.get('sex',''))
	openid = request.GET.get('openid',request.POST.get('openid',''))
	nickname = request.GET.get('nickname',request.POST.get('nickname',''))
	type = request.GET.get('type',request.POST.get('type',''))
	signature = request.GET.get('signature',request.POST.get('signature',''))
	image_url = request.GET.get('image_url',request.POST.get('image_url',''))
	if request.method == 'POST':
		form = BindEmail(request.POST)
		if form.is_valid():
			openid = form.cleaned_data['openid']
			nickname = form.cleaned_data['nickname']
			email = form.cleaned_data['email']
			password = form.cleaned_data['password']
			type = form.cleaned_data['type']
			signature = form.cleaned_data['signature']
			image_url = form.cleaned_data['image_url']
			sex = form.cleaned_data['sex']
			users = User.objects.filter(email = email)
			if users:
				user = users[0]
			else:
				while User.objects.filter(username=nickname):
					nickname = nickname + '*'
				user = User(username=nickname,email=email,sex=sex,signature=signature)
				user.set_password(password)
				user.is_active = True
				user.download_image(image_url,nickname)
				user.save()
			oauth_ex = OAuth_ex(user=user,openid=openid,type=type)
			oauth_ex.save()
			auth_login(request,user,backend='django.contrib.auth.backends.ModelBackend')
			data={}
			data['goto_url'] = '/'
			data['goto_time'] = 10000
			data['goto_page'] = True
			data['message_title'] = '绑定账号成功'
			data['message'] = u'绑定成功!您的用户名为:<b>%s</b>。您现在可以同时使用本站账号和此第三方账号登录本站了!' % nickname
			return render_to_response('oauth/response.html',data)
	else:
		form = BindEmail(initial={
			'openid':openid,
			'nickname':nickname,
			'type':type,
			'signature':signature,
			'image_url':image_url,
			'sex':sex,
		})
	return render(request,'oauth/form.html',context={'form':form,'nickname':nickname,'type':type})

表单

from django import forms
from users.models import User
from django.core.exceptions import ValidationError
from django.contrib.auth import authenticate
from .models import OAuth_ex


class BindEmail(forms.Form):
	sex = forms.CharField(widget=forms.HiddenInput(attrs={'id':'sex'}))
	image_url = forms.CharField(widget=forms.HiddenInput(attrs={'id':'image_url'}),required=False)
	signature = forms.CharField(widget=forms.HiddenInput(attrs={'id':'signature'}),required=False)
	type = forms.CharField(widget=forms.HiddenInput(attrs={'id':'type'}))
	openid = forms.CharField(widget=forms.HiddenInput(attrs={'id':'openid'}))
	nickname = forms.CharField(widget=forms.HiddenInput(attrs={'id':'nickname'}))
	email = forms.EmailField(label=u'绑定邮箱',widget=forms.EmailInput(attrs={'class':'form-control','id':'email','placeholder':u'请输入用于绑定本站账号的邮箱','oninvalid':"setCustomValidity('请输入正确的邮箱地址');",'oninput':"setCustomValidity('');"}))
	password = forms.CharField(label=u'用户密码',widget=forms.PasswordInput(attrs={'class':'form-control','id':'password','placeholder':u'若尚未注册过本站账号,则该密码作为账户密码',"oninvalid":"setCustomValidity('请输入绑定用户的密码');",'oninput':"setCustomValidity('');"}))
	
	def clean_email(self):   #查询邮箱是否已经被绑定
		email = self.cleaned_data.get('email')
		type = self.cleaned_data.get('type')
		users = User.objects.filter(email=email)
		if users:
			if OAuth_ex.objects.filter(user=users[0],type=type):
				raise ValidationError(u'邮箱已经被绑定了')
		return email
		
	def clean_password(self):   #验证密码是否输入正确
		email = self.cleaned_data.get('email')
		password = self.cleaned_data.get('password')
		users = User.objects.filter(email=email)
		if users:
			user = authenticate(email=email,password=password)
			if user:
				return password
			else:
				raise ValidationError(u'密码不正确,绑定失败')
		return password

前端

登录界面

<div id="oauth_button">
	<div class="pull-right">
		<span>其他账号登录:</span>
		<a href="{% url 'oauth:github_login' %}"><img class="type" src="{% static 'images/github.png' %}" alt="GITHUB" /></a>&nbsp;
		<a href="{% url 'oauth:qq_login' %}"><img class="type" src="{% static 'images/tx.png' %}" alt="QQ" /></a>&nbsp;
		<a href="{% url 'oauth:weibo_login' %}"><img class="type" src="{% static 'images/weibo.png' %}" alt="WEIBO" /></a>
	</div>
</div>

绑定用户界面

<div class="col-md-5 col-sm-6 col-xs-12">
	<div class="well">
		<h2>绑定账号</h2>
		<p>欢迎您登录:
		{% if type == '1' %}
			<img class="type" src="{% static 'images/github.png' %}" alt="GITHUB" />
		{% elif type == '2' %}
			<img class="type" src="{% static 'images/tx.png' %}" alt="QQ" />
		{% elif type == '3' %}
			<img class="type" src="{% static 'images/weibo.png' %}" alt="WEIBO" />
		{% endif %}
		<b>{{nickname}}</b>,请您绑定本站账号</p>
		<form role="form" action="{% url 'oauth:bind_email' %}" method="post">
			{% csrf_token %}
			{% for field in form %}
				{% if field.is_hidden %}
					{{field}}
				{% else %}
				<div class="form-group">
					{{ field.label_tag }}
					{{ field }}
					{{ field.errors }}
				</div>
				{% endif %}
			{% endfor %}
			{{ form.non_field_errors }}
			<div class="right">
				<button id="register" type="submit" class="btn btn-default pull-right">绑定</button>
			</div>
		</form>
	</div>
</div>

反馈信息界面

<body>
	<div class="container">
		<h2>{{message_title|safe}}</h2>
		<p class="text-center">
             {{message|safe}}
           </p>
		{% if goto_page %}
		<p class="text-center">
			本页面在 <b><span id="time_left"></span></b> 秒后自动跳转,若未跳转,请点击<a href="{{goto_url}}">此处</a>
           </p>
		{% endif %}
	</div>
	<script src="https://cdn.bootcss.com/jquery/2.1.1/jquery.min.js"></script>
	<script src="https://cdn.bootcss.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
	<script src="{% static 'js/javascript.js' %}"></script>
	<script type="text/javascript">
		{% if goto_page %}   <!-- 自动页面跳转 -->
			$(function(){
				var time = {{goto_time}} / 1000;
				intervalid = window.setInterval(function(){
					if (time <= 0){
						clearInterval(intervalid);
						window.location = '{{goto_url}}';
					}
					$('#time_left').text(time);
					time -= 1;
				},1000);
			});
		{% endif %}
	</script>
</body>

效果

这里写图片描述
这里写图片描述

参考自http://yshblog.com/blog/60


如果觉得这篇文章帮助了您,请打赏一个小红包鼓励作者继续创作哦!!!

在这里插入图片描述

  • 5
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值