OAuth2.0认证过程是一个三方协作的过程。这三方包括用户、第三方服务器、授权服务器。
本文通过python实现OAuth2.0的的认证过程。为了简化难度,我们将第三方服务器集成在授权服务器里面。现实中,是不可能这么做的,因为只要域名一致,网站就可以通过cookies存储用户的用户名和密码,也就不存在认证的环节了。所以,本文不存储cookies。
1、OAuth的流程介绍
首先,用户登陆client程序。在用户登陆的时候,client将用户登陆重定向到Authorization Server,并附上client的ID和URI。
随后,Authorization Server要向用户确认是否给client授权。方法是让用户向Authorization Server提供用户名和密码。
接着,Authorization Server收到用户的授权后,会重定向到client的URL,并附带Authorization code。
之后,client使用Authorization code和URI向Authorization Server请求token。
最后,Authorization Server验证Authorization code和URI之后,向client发送token。
如何确认token发送成功呢?
2、本文需要实现的功能
按照上一节的流程,逐步实现oauth授权服务器的功能。
新建一个客户端,并保存到用户字典:
user = {
'liuchunming': ['12345']
}
client_id = '1234567890'
user[client_id] = []
Authorization Server需要保存重定向的uri和授权码,我们建立保存uri和授权码的变量:
auth_code = {}
oauth_redirect_uri = []
client端重用于获取token的uri
redirect_uri='http://localhost:5000/client/passport'
2.1、cleint端实现重定向功能(步骤A)
flask中使用 redirect() 函数可以实现重定向。
首先,创建一个/client/login的路由,用于用户登陆client。该路由里面定义一个client_login函数,该函数用于将访问‘/client/login‘的请求重定向到’http://localhost:5000/oauth‘。该函数需要为Authorization Server提供client_id和redirect_uri。
@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)
我们先创建一个简单的/oauth路由,用来验证/client/login会不会重定向到这个路由。
@app.route('/oauth',methods=['POST','GET'])
def oauth():
return 'this is an authorization servier,please login'
现在编写一段验证程序:
import requests
r = requests.get('http://127.0.0.1:5000/client/login')
print r.text
print r.history
该程序如果输出:
this is an authorization servier,please login
[<Response [302]>]
就说明我们的重定向功能已经完成。至此步骤A功能就实现了。
2.2、授权服务器需要实现保存redirect_uri功能
授权服务器器需要将client的redirect_uri保存下来,用以后续验证Authorization code的正确性。
oauth_redirect_uri = []
@app.route('/oauth', methods=['POST', 'GET'])
def oauth():
if request.args.get('redirect_uri'):
oauth_redirect_uri.append(request.args.get('redirect_uri'))
2.3、授权服务器需要实现发放授权码功能(步骤B和C)
当用户给予授权服务器授权之后,授权服务器生成Authorization code给client。这个过程有两个步骤,第一授权服务器要获得用户的授权,第二授权服务器发放Authorization code给client。第一步,授权服务器通过让用户输入在授权服务器里面的用户名和密码,来获得用户的授权。第二步,在获得用户授权之后生成Authorization code,并重定向到client提供的uri,将授权码发出去。
我们将2.2中的oauth函数扩展如下:
@app.route('/oauth', methods=['POST', 'GET'])
def oauth():
<pre name="code" class="python"> if request.args.get('redirect_uri'):
oauth_redirect_uri.append(request.args.get('redirect_uri'))
if request.args.get('user'): if user.get(request.args.get('user'))[0] == request.args.get('pw') and oauth_redirect_uri: uri = oauth_redirect_uri[0] + '?code=%s' % gen_auth_code(oauth_redirect_uri[0]) return redirect(uri)
2.4、授权服务器实现生成授权码功能
首先,定义一个生成授权码的函数。该函数生成一个授权码,并将授权码与client的URI对应起来,形成一个字典。
其次,需要定义一个字典变量,将授权码和URI存储起来到字典中,用于后续Authorization Server发送token给client的时候,对client进行验证。
auth_code = {}
def gen_auth_code(uri):
code = random.randint(1,1000)
auth_code[code] = uri
return code
2.5、client实现请求token的功能(步骤D)
client通过2.3中生成的Authorization code、redirect_uri和client_id向授权服务器请求token。
该功能我们在‘/client/passport’路由中实现,:
@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, redirect_uri, client_id)
return redirect(uri)
2.6、授权服务器发放token(步骤E)
授权服务器收到Authorization code和redirect_uri,并验证其正确性之后发放token。验证Authorization code和redirect_uri正确性的方法是,对比请求头中的Authorization code和redirect_uri与在授权服务器中存储的Authorization code和redirect_uri是否一致。将oauth方法更新如下:
@app.route('/oauth', methods=['POST', 'GET'])
def oauth():
if request.args.get('redirect_uri'):
oauth_redirect_uri.append(request.args.get('redirect_uri'))
if request.args.get('user'):
if user.get(request.args.get('user'))[0] == request.args.get('pw') and oauth_redirect_uri:
uri = oauth_redirect_uri[0] + '?code=%s' % gen_auth_code(oauth_redirect_uri[0])
return redirect(uri)
if request.args.get('code'):
if auth_code.get(int(request.args.get('code'))) == request.args.get('redirect_uri'):
return gen_token(request.args.get('client_id'))
3、验证
编写测试程序
import requests
r = requests.get('http://127.0.0.1:5000/client/login')
print r.text
print r.history
print r.url
login_uri = r.url.split('?')[0] + '?user=liuchunming&pw=12345'
r2 = requests.get(login_uri)
print r2.text
print r2.history
r = requests.get('http://127.0.0.1:5000/testlogin',params={'token': r2.text})
print r.text
返回:
C:\Python27\python.exe C:/Users/Administrator/PycharmProjects/flaskexample/request.py
please login
[<Response [302]>]
http://localhost:5000/oauth?response_type=code&client_id=1234567890&redirect_uri=http://localhost:5000/client/passport
MTIzNDU2Nzg5MDowLjY1MjY4MjgxOTg0MjoxNDMxMDc5Mzc2LjU1
[<Response [302]>, <Response [302]>]
data
Process finished with exit code 0
源码:
https://github.com/liuchunming033/oauth2.0