OAuth介绍及实现(上)

1. OAuth 2.0 原理介绍

1.1 OAuth 2.0 概述

        OAuth(开放授权) 是一个正式的互联网标准协议,OAuth 2.0 是它的后续版本,主要用于解决无需用户提供用户名和密码时,第三方应用程序能够获取用户存储在服务器提供商那里的某些数据(头像、用户名等)。例如,常用的使用微信、QQ或微博账号登录其他网站等情形。OAuth允许用户提供一个令牌(Token)给第三方网站,一个令牌对应一个特定的网站,同时该令牌只能在特定的时间内访问特定的资源

授权请求A
令牌请求 B
资源请求C
同意授权D
授予Token E
给予资源F
第三方网站
用户
授权服务器发放Token
资源服务器

  • C和D可以在同一个服务器上
  • 令牌有时效性和有效性,用户可以手动管理Token的有效性
1.2 OAuth 几种授权模式
  • 授权码模式

            后台服务器与服务提供商的服务器进行互动来获取Token,这个过程不通过用户的浏览器,是最安全的一种方法。

  • 简化模式

        没有授权流程中的C/D步骤,而是用户浏览器通过资源服务器发送的代码提取token,并直接发送给第三方应用程序。

  • 密码模式

        用户必须把密码给客户端,但是客户端不得存储密码,通常用于用户对客户端高度信任的情况下。比如客户端是操作系统的一部分,或者是由一个著名公司出品的产品,而认证服务器也只有在其他模式无法执行的情况下才考虑这种模式。

  • 客户端模式

        用户直接向客户端注册,客户端以自己的名义要求服务提供商提供服务,这其实不存在授权的问题。

1.3 OAuth 授权码模式

        授权码模式是功能最完整、流程最严密的授权模式;简化模式不通过第三方应用程序的服务器,直接在浏览器中向认证服务器申请令牌,条狗了"授权码"这个步骤;密码模式需要用户向客户端提供自己的用户名和密码;客户端以自己的名义而不是以用户的名义向“服务提供商”进行认证。后几种模式都是授权码模式的简化版,我们这里着重讲解授权码模式。

2. 使用Python编写简单的基本功能

        前面已经提到,OAuth 2.0 授权是一个三方协作的过程,为了进一步简化编码的难度,我们将第三方应用、授权服务器和资源服务器都集成在一台主机上面(也就是我们的个人编码电脑)的Flask程序上。当然,现实中是没有人这么做的,因为只要域名一致,网站就可以同村Cookie存取账号和密码信息以实现登录。不过,在这里,我们故意不存储Cookie信息,以模拟整个OAuth 2.0 的流程。这小节我们着重讲解授权服务器、资源服务器和客户端程序(第三方应用)的编码。

        本小节内容,包含以下两点知识:

  • 实现重定向机制
  • 实现授权码机制
  1. 服务端代码 app.py
#! /usr/bin/env python
# -*- coding: utf-8 -*-
'''
    Created on 2019年2月20日
    @author: 杨现 成都天府机场建设指挥部
'''

import base64,random,time
from flask import Flask,request,redirect

# 定义APP应用
app =Flask(__name__)

# 定义用户名和密码
users={
    "yangxian":["123456"]
}

auth_code={  
}

# 生成token的函数,自定义一种token的格式
def gen_token(uid):
    token=base64.b64encode(':'.join([str(uid), str(random.random()),str(time.time() + 7200)]))
    users[uid].append(token)
    return token

# 验证Token的有效性
def verify_token(token):
    _token=base64.b64decode(token)   # 解密token
    if not users.get(_token.split(':')[0])[-1]==token:
        return -1
    if float(_token.split(':')[-1]) >= time.time():
        return 1
    else:
        return 0
    
# 定义路由,即访问uri为:"http://localhost:5000/index"
# 5000是flask的默认端口
@app.route('/index',methods=['POST','GET'])
def index():
    print(request.headers)
    return 'hello'

# 登录时,如果用户名和密码匹配,则生成令牌
@app.route('/login',methods=['POST','GET'])
def login():
    ori_use=request.headers['Authorization'].split(' ')[-1]
    uid,pwd=bytes.decode(base64.b64decode(ori_use)).split(':')
    if users.get(uid)[0]==pwd:  # 如果用于的第一个密码等于此密码
        return gen_token(uid)
    else:
        return 'error'

@app.route('/test',methods=['POST','GET'])
def test():
    token=request.args.get('token')
    if verify_token(token)==1:
        return 'data'   # 返回数据,即资源管理服务器的功能
    else:
        return 'error'  # 返回错误信息

# 客户端登录的逻辑
"""
   /client/login  相当于第三方网站
   /oauth         相当于授权方,只是这里写到一起了,为了方便
"""
@app.route('/client/login',methods=['POST','GET'])
def client_login():
    uri="http://localhost:5000/oauth"
    return redirect(uri)

# token发放机制
@app.route('/oauth',methods=['POST','GET'])
def oauth():
    # 授权码的发放
    if request.args.get('code'):
        # 如果授权码的对应的uri请求中的uri,则为其用户生成令牌
        if auth_code.get(int(request.args.get('code')))==request.args.get('redirect_uti'):
            return gen_token(request.args.get('client_id'))
    return "please login"

# 生成授权码方法
def gen_code(uri):
    code=random.randint(0,10000)
    auth_code[code]=uri   # 授权码对应的uri
    return code  

if __name__=="__main__":
    app.run(debug=True)
  1. 用户端代码 request_t.py
#! /usr/bin/env python
# -*- coding: utf-8 -*-
'''
    Created on 2019年2月20日
    @author: 杨现 成都天府机场建设指挥部
'''

import requests
r=requests.get('http://127.0.0.1:5000/client/login',auth=('yangxian','123456'))
# print(r.text)
print(r.history)   # 可以显示重定向的情况

3. 使用Flask实现简单的OAuth 2.0授权服务器

  • 整合并优化之前的代码
  • 讲解Requests 验证结果的步骤
  • 启动服务器,并通过Requests进行验证
  1. 服务端代码 app.py (在上一节的代码中添加了部分代码)
#! /usr/bin/env python
# -*- coding: utf-8 -*-
'''
    Created on 2019年2月20日
    @author: Lenovo
'''

import base64,random,time
from flask import Flask,request,redirect

# 定义APP应用
app =Flask(__name__)

# 定义用户名和密码
users={
    "yangxian":["123456"]
}

client_id='123456'
users[client_id]=[]
auth_code={ }
oauth_redirect_uri=[]
redirect_uri="http://localhost:5000/client/passport"

# 生成token的函数,自定义一种token的格式
def gen_token(uid):
    token=base64.b64encode(':'.join([str(uid), str(random.random()),str(time.time() + 7200)]))
    users[uid].append(token)
    return token

# 验证Token的有效性
def verify_token(token):
    _token=base64.b64decode(token)   # 解密token
    if not users.get(_token.split(':')[0])[-1]==token:
        return -1
    if float(_token.split(':')[-1]) >= time.time():
        return 1
    else:
        return 0
    

# 定义路由,即访问uri为:"http://localhost:5000/index"
# 5000是flask的默认端口
@app.route('/index',methods=['POST','GET'])
def index():
    print(request.headers)
    return 'hello'


# 登录时,如果用户名和密码匹配,则生成令牌
@app.route('/login',methods=['POST','GET'])
def login():
    ori_use=request.headers['Authorization'].split(' ')[-1]
    uid,pwd=bytes.decode(base64.b64decode(ori_use)).split(':')
    if users.get(uid)[0]==pwd:  # 如果用于的第一个密码等于此密码
        return gen_token(uid)
    else:
        return 'error'


@app.route('/test',methods=['POST','GET'])
def test():
    token=request.args.get('token')
    if verify_token(token)==1:
        return 'data'   # 返回数据,即资源管理服务器的功能
    else:
        return 'error'  # 返回错误信息


# 客户端登录的逻辑
"""
   /client/login  相当于第三方网站
   /oauth         相当于授权方,只是这里写到一起了,为了方便
"""
@app.route('/client/login',methods=['POST','GET'])
def client_login():
    uri="http://localhost:5000/oauth?response_type=code&client_id=%s&redirect_uri=%s"%(client_id,redirect_uri)
    return redirect(uri)

# token发放机制
@app.route('/oauth',methods=['POST','GET'])
def oauth():
    # 授权码的发放
    if request.args.get('code'):
        # 如果授权码的对应的uri请求中的uri,则为其用户生成令牌
        if auth_code.get(int(request.args.get('code')))==request.args.get('redirect_uti'):
            return gen_token(request.args.get('client_id'))
    if request.args.get('user'):
        if users.get(request.args.get('user'))[0]==request.args.get('pw') and oauth_redirect_uri:
            uri=oauth_redirect_uri[0]+'?code=%s' %gen_code(oauth_redirect_uri[0])
            return redirect(uri)
    if request.args.get('redirect_uri'):
        oauth_redirect_uri.append(request.args.get('redirect_uri'))
    return "please login"



@app.route('/client/passport',methods=['POST','GET'])
def client_passport():
    code=request.args.get('code')
    uri='http://localhost:5000/oauth?grant_type=authorization_code&code=%s&redirect_uri=%s&client_id=%s'%(code,client_id)
    return redirect(uri)

# 生成授权码方法
def gen_code(uri):
    code=random.randint(0,10000)
    auth_code[code]=uri   # 授权码对应的uri
    return code  


if __name__=="__main__":
    app.run(debug=True)
   
  1. 客户端代码
#! /usr/bin/env python
# -*- coding: utf-8 -*-
'''
    Created on 2019年2月20日
    @author: Lenovo
'''

import requests

r=requests.get("http://localhost:5000/client/login")
print(r.text)
print('======================')
print(r.history)
print('======================')
print(r.url)
print('======================')
uri_login=r.url.split('?')[0]='?user=yangxian&pw=123456'
r2=requests.get(uri_login)
print(r2.text)
print(r2.history)
print('======================')
r=requests.get("http://127.0.0.1:5000/test",params={'token':r2.text})
print(r.text)

4. 总结

        本课程我们学习了OAuth 2.0的一种实现方式,我们应当掌握以下知识:

  • 清楚OAuth 2.0 协议的内容
  • 掌握Flask重定向的操作
  • 掌握OAuth 2.0 协议中的授权码模式

如果想继续提高,请参考《OAuth介绍及实现(上)》的内容。


作者信息

作者信息
本部分内容参考自极客学院网络视频课程!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值