目录
1.oauth认证原理
-
OAuth是一个开放标准,允许用户让第三方应用访问该用户在某一网站上存储的私密的资源,而无需将用户名和密码提供给第三方应用。
-
OAuth允许用户提供一个令牌,而不是用户名和密码来访问他们存放在特定服务提供者的数据。
-
这个code如果能出三方换取到数据就证明这个用户是三方真实的用户
2.第三方登录与本地登陆的关联(三种情况)
2.1
情况1: 本地未登录,第一次登录第三方
此时相当于注册,直接把第三方信息拉取来并注册成本地用户就可以了,并建立本地用户与第三方用户(openid)的绑定关系
2.2 情况2:本地未登录,再次登录第三方
此时用户已注册,获取到openid后直接找出对应的本地用户即可
2.3 情况3:本地登录,并绑定第三方
这个只要将获取到的openid绑定到本地用户就可以了
3.第三方微博登录流程图
3.1 前端获取认证code
-
1.在Vue页面加载时
动态发送请求获取微博授权url
-
2.flask收到请求的url后,通过微博
应用ID(client_id)和回调地址(redirect_uri)
动态生成授权url返回给Vue
-
3.当用户点击上面的url进行扫码,授权成功会
跳转我们的回调界面并附加code参数
-
4.Vue获取到微博返回的code后,会
将code发送给flask后端
(上面的redirect_uri)
3.2 获取微博access_token
-
后端获取code后,结合client_id、client_secret、redirect_uri参数进行传递,获取微博access_token
3.3 获取微博用户基本信息并保存到数据库
-
使用获得的access_token调用获取用户基本信息的接口,
获取用户第三方平台的基本信息
-
用户基本信息
保存到数据库,然后关联本地用户
,然后将用户信息返回给前端
3.4 生成token给Vue
-
flask后端借助微博认证成功后,可以
使用JWT生成token
,返回给Vue -
Vue将token存储到localStorage中
,以便用户访问其他页面进行身份验证
第三方登录流程图
4.qq第三方登录开发者网站:网站:QQ互联官网首页
5.后端流程:
""" 1. 生成qq扫码登录的url,返回给前端用户 2. 用户扫码,进行登录, qq对用户进行鉴权 3. 用户在手机上进行确认登录,返回一个code,code代表的是登录成功的信心 4. code 要返回给网站, 网站拿到后判断是否有账号绑定 1. 绑定过过账号, 直接登录成功 2. 没有绑定过账号: 1. 直接输入账号密码进行绑定, 网站判断账号密码是否正确,正确就绑定,不正确就报错 2. 没有账号信息, 要先注册账号,注册后载绑定 3. 绑定成功后,相当于登录成功, 要返回token给前端用户 5. 用户携带token进行下一步请求 """
6.后端使用钉钉完成三方登录代码展示:
6.1配置
# 钉钉登录相关配置
DING_APP_ID = 'dingoajf8cqgyemqarekhr'
DING_REDIRECT_URI = 'http://127.0.0.1:8080/dingding_back'
DING_SECRET_KEY = 'Fcah25vIw-koApCVN0mGonFwT2nSze14cEe6Fre8i269LqMNvrAdku4HRI2Mu9VK'
6.2功能实现
import base64
import hmac
import urllib
from hashlib import sha256
import json, time
import requests
from urllib.parse import parse_qs, urlencode
from flask import Blueprint,current_app,jsonify
from flask_restful import Api,Resource,reqparse
from werkzeug.security import check_password_hash
from common.models.user import OauthUser,UserModels
from common.models import db
from common.utils.jwt_utils import _generate_token
oauth_user_bp = Blueprint('oauth_bp', __name__,url_prefix='/oauth2')
api = Api(oauth_user_bp)
"""
1. 前端页面点击钉钉图片, 后端返回给用户一个扫码登录的页面
2. 用户扫码登录, 确认登录后,返回给用户一个:http://127.0.0.1:8080/dingding_back?code=b61cf0c1f5e73df298243c2ba4a9a62f&state=STATE
3. 前端要把url中code的值获取到传递后端, 后端根据code 的值获取钉钉账号的信息
4. 根据钉钉账号的信息,判断是否和后台中的账号是否绑定
1. 绑定直接登录
2.没有绑定, 返回给前端一个绑定账号的页面,
3. 用户输入后台的账号信息,进行绑定
5. 返回绑定后的token, 用户信息
"""
class DingDingOauthResource(Resource):
def get(self):
base_url = 'https://oapi.dingtalk.com/connect/qrconnect?appid=%s&response_type=code&scope=snsapi_login&state=STATE&redirect_uri=%s'
print(current_app.config.get('DING_REDIRECT_URI'), current_app.config.get('DING_APP_ID'))
url = base_url % (current_app.config.get('DING_APP_ID'), current_app.config.get('DING_REDIRECT_URI'))
# print('url>>',url)
return {'url': url, 'code': 200}
class DingdingUserInfo(Resource):
def get_ding_user(self, code):
"""获取登录的钉钉用户信息"""
base_url = "https://oapi.dingtalk.com/sns/getuserinfo_bycode?signature="
appid = current_app.config.get('DING_APP_ID')
appSecret = current_app.config.get('DING_SECRET_KEY')
t = time.time()
# 把时间戳转换成毫秒
timestamp = str(int(round(t*1000)))
# 构造签名
signature = base64.b64encode(
hmac.new(appSecret.encode('utf-8'), timestamp.encode('utf-8'), digestmod=sha256).digest())
# params 是查询字符串参数
# data 是请求体参数
# header 指明传递参数的类型
# TODO 构造参数错误
# 拼接url
url = base_url + urllib.parse.quote(
signature.decode('utf-8')) + "×tamp=" + timestamp + "&accessKey=" + appid
print("url>>>>", url)
resp = requests.post(url, data=json.dumps({'tmp_auth_code': code}),
headers={'Content-Type': 'application/json'})
print("ding的用户 resp>>>", resp.json())
return resp.json()
def get(self):
"""根据前端传来的code, 获取钉钉登录的用户信息"""
parser = reqparse.RequestParser()
parser.add_argument('code')
args = parser.parse_args()
code = args.get('code')
print('code>>>>', code)
# 根据code获取钉钉用户信息
ding_user = self.get_ding_user(code)
if ding_user['errcode'] != 0:
return jsonify(msg="获取钉钉用户信息失败", code=500)
# 获取钉钉的openid
openid = ding_user['user_info']['openid']
if openid:
# 根据openid 判定是否绑定账号
oauth_user = OauthUser.query.filter_by(uid=openid).first()
if oauth_user:
# 查询到用户,已经绑定过,
user = UserModels.query.filter_by(uid=oauth_user.user).first()
# 返回绑定成功的用户token 及id
token, refresh_token = _generate_token({'account': user.account, 'user_id': user.uid,
'is_superuser': user.is_superuser})
print('登陆的token', token)
return jsonify(msg="登陆成功", data={'code': 200, 'token': token, 'refresh': refresh_token,
'account': user.account, 'uid': user.uid})
else:
return jsonify(msg="没有绑定用户,请先绑定", data={'uid': openid})
def post(self):
"""绑定账号"""
parser = reqparse.RequestParser()
parser.add_argument('account')
parser.add_argument('password')
parser.add_argument('unid')
args = parser.parse_args()
uname = args.get('account')
pwd = args.get('password')
openid = args.get('unid')
# 判断账号是否注册过,没有注册过去注册
print("?????", uname, pwd, openid)
user = UserModels.query.filter_by(account=uname).first()
if not user:
return jsonify(msg="请先注册网站", code=400)
# 校验密码
rest = check_password_hash(user.password, pwd)
if not rest:
return jsonify(msg="密码错误", code=400)
# 根据user 查询oauthuser表中的数据, 绑定第三方账号
oauth = OauthUser(image='', uid=openid, user=user.uid, oauth_type='qq')
db.session.add(oauth)
db.session.commit()
# 返回绑定成功的用户token 及id
token, refresh_token = _generate_token({'account': user.account, 'user_id': user.uid,
'is_superuser': user.is_superuser})
print('登陆的token', token)
return jsonify(msg="绑定成功", code=200, data={'token': token, 'refresh': refresh_token,
'account': user.account, 'uid': user.uid})
api.add_resource(DingDingOauthResource, '/ding_url')
api.add_resource(DingdingUserInfo,'/ding_user')
7.为什么使用三方登录?
-
服务方希望用户注册, 而用户懒得填注册时的各种信息(主要是为了保证用户的唯一性,各种用户名已占用,密码格式限制).
-
而像微信, QQ, 微博等几乎每个人都会安装的应用中用户肯定会在其中某一个应用中已经注册过,证明该用户在已经注册的应用中的唯一性.
-
第三方登录的实质就是在授权时获得第三方应用提供的代表了用户在第三方应用中的唯一性的openid.并将openid储存在第三方服务控制的本地储存.