1.什么是oauth?
2.什么時候用oauth吼鳞?
3.oauth用在哪里革砸?
4.oauth怎么用氮墨?
5.一個小demo
6.demo解析
1.什么是oauth?
oauth協(xié)議為用戶資源的授權(quán)提供了一個安全的栗弟、開放而又建議的標準污筷。oauth的授權(quán)不會是第三方初級到用戶的賬號信息(如用戶名與密碼),及第三方無需使用用戶的用戶名與密碼就可以申請獲得該用戶資源的授權(quán)乍赫,因此oauth是安全的瓣蛀。oauth是Open Authorization的簡寫
2.什么時候用oauth?
為了理解OAuth的適用場合雷厂,讓我舉一個假設(shè)的例子惋增。
有一個"云沖印"的網(wǎng)站,可以將用戶儲存在Google的照片改鲫,沖印出來诈皿。用戶為了使用該服務(wù),必須讓"云沖印"讀取自己儲存在Google上的照片钩杰。
問題是只有得到用戶的授權(quán)纫塌,Google才會同意"云沖印"讀取這些照片。那么讲弄,"云沖印"怎樣獲得用戶的授權(quán)呢措左?
傳統(tǒng)方法是,用戶將自己的Google用戶名和密碼避除,告訴"云沖印"怎披,后者就可以讀取用戶的照片了。這樣的做法有以下幾個嚴重的缺點瓶摆。
(1)"云沖印"為了后續(xù)的服務(wù)凉逛,會保存用戶的密碼,這樣很不安全群井。
(2)Google不得不部署密碼登錄状飞,而我們知道,單純的密碼登錄并不安全。
(3)"云沖印"擁有了獲取用戶儲存在Google所有資料的權(quán)力诬辈,用戶沒法限制"云沖印"獲得授權(quán)的范圍和有效期酵使。
(4)用戶只有修改密碼,才能收回賦予"云沖印"的權(quán)力焙糟。但是這樣做口渔,會使得其他所有獲得用戶授權(quán)的第三方應(yīng)用程序全部失效。
(5)只要有一個第三方應(yīng)用程序被破解穿撮,就會導致用戶密碼泄漏缺脉,以及所有被密碼保護的數(shù)據(jù)泄漏。
OAuth就是為了解決上面這些問題而誕生的悦穿。
3.oauth用在哪里攻礼?
4.oauth怎么用?
OAuth在"客戶端"與"服務(wù)提供商"之間咧党,設(shè)置了一個授權(quán)層(authorization layer)秘蛔。"客戶端"不能直接登錄"服務(wù)提供商",只能登錄授權(quán)層傍衡,以此將用戶與客戶端區(qū)分開來。"客戶端"登錄授權(quán)層所用的令牌(token)负蠕,與用戶的密碼不同蛙埂。用戶可以在登錄的時候,指定授權(quán)層令牌的權(quán)限范圍和有效期遮糖。
"客戶端"登錄授權(quán)層以后绣的,"服務(wù)提供商"根據(jù)令牌的權(quán)限范圍和有效期,向"客戶端"開放用戶儲存的資料欲账。
(A)用戶打開客戶端以后屡江,客戶端要求用戶給予授權(quán)。
(B)用戶同意給予客戶端授權(quán)赛不。
(C)客戶端使用上一步獲得的授權(quán)惩嘉,向認證服務(wù)器申請令牌。
(D)認證服務(wù)器對客戶端進行認證以后踢故,確認無誤文黎,同意發(fā)放令牌。
(E)客戶端使用令牌殿较,向資源服務(wù)器申請獲取資源耸峭。
(F)資源服務(wù)器確認令牌無誤,同意向客戶端開放資源淋纲。
客戶端的授權(quán)模式:
客戶端必須得到用戶的授權(quán)(authorization grant)劳闹,才能獲得令牌(access token)。OAuth 2.0定義了四種授權(quán)方式。
- 授權(quán)碼模式(authorization code)
- 簡化模式(implicit)
- 密碼模式(resource owner password credentials)
- 客戶端模式(client credentials)
5.一個小demo
'''
import base64
import random
import time
import json
import hmac
from datetime import datetime, timedelta
from flask import Flask, request, redirect, make_response
app = Flask(__name__)
users = {
'lisir': ['123456']
}
redirect_uri = 'http://localhost:8888/client/passport'
client_id = '666666'
# 添加用戶名到users字典中
users[client_id] = []
auth_code = {}
# 存放重定向uri
oauth_redirect_uri = []
#過期時間
timeout = 3600 * 2
#新版本token生成器
def get_token(data):
'''獲取token'''
data = data.copy()
if "salt" not in data:
#隨機生成的一個實數(shù)本涕,在[0,1)范圍內(nèi)
data['salt'] = random.random()
if "expires" not in data:
#設(shè)置過期時間
data['expires'] = time.time() + timeout
payload = json.dumps(data).encode('utf8')
#生成簽名
sig = _get_signature(payload)
return encode_token_bytes(payload+sig)
def get_code(uri, user_id):
'''授權(quán)碼生成器'''
code = random.randint(0, 10000)
auth_code[code] = [uri, user_id]
return code
def verify_token(token):
'''token驗證'''
decode_token = decode_token_bytes(token)
payload = decode_token[:-16]
sig = decode_token[-16:]
#生成簽名
expected_sig = _get_signature(payload)
if sig != expected_sig:
print('token驗證失敗')
return {}
data = json.loads(payload.decode('utf8'))
print(data)
if data.get('expires') >= time.time():
return data
return 0
#使用hmac為消息生成簽名
def _get_signature(value):
a = hmac.new(b'secret1234656', value).digest()
return a
#base64編碼
def encode_token_bytes(data):
b = base64.urlsafe_b64encode(data)
print(b)
return b
def decode_token_bytes(data):
return base64.urlsafe_b64decode(data)
@app.route('/index', methods=['POST', 'GET'])
def index():
print(request.headers)
return "hello"
@app.route('/login', methods=['GET', 'POST'])
def login():
uid, pw = base64.b64decode(request.headers['Authorization'].split(' ')[-1]).split(':')
if users.get(uid)[0] == pw:
return get_token(dict(user=uid, pw=pw))
else:
return 'error'
@app.route('/oauth', methods=['GET', 'POST'])
def oauth():
#處理表單登錄业汰,同時設(shè)置cookie
if request.method == "POST" and request.form['user']:
u = request.form['user']
p = request.form['pw']
if "".join(users.get(u)) == p and oauth_redirect_uri:
uri = oauth_redirect_uri[0] + '?code=%s' % get_code(oauth_redirect_uri[0], u)
print(uri)
expire_date = datetime.now() + timedelta(minutes=1)
#設(shè)置cookie
#make_response接受字符串或錯誤碼會返回一個response對象
resp = make_response(redirect(uri))
resp.set_cookie('login', '_'.join([u, p]), expires=expire_date)
return resp
#驗證授權(quán)碼,發(fā)放token
if request.args.get('code'):
auth_info = auth_code.get(int(request.args.get('code')))
if auth_info[0] == request.args.get('redirect_uri'):
#可以在授權(quán)碼的auth_code中存儲用戶名偏友,曾進token中
return get_token(dict(client_id=request.args.get('client_id'), user_id=auth_info[1]))
#如果登錄用戶有Cookie蔬胯,則直接驗證成功,否則需要填寫登錄表單
if request.args.get('redirect_uri'):
oauth_redirect_uri.append(request.args.get('redirect_uri'))
print(oauth_redirect_uri)
if request.cookies.get('login'):
u, p = request.cookies.get('login').split('_')
if users.get(u)[0] == p:
uri = oauth_redirect_uri[0] + '?code=%s' % get_code(oauth_redirect_uri[0], u)
print(u, p)
print(uri)
return redirect(uri)
return '''
<form action='/oauth' method='POST'>
<p><input type=text name=user>
<p><input type=text name=pw>
<p><input type=submit value=Login>
'''
@app.route('/client/login', methods=['POST', 'GET'])
def client_login():
uri = '/oauth?response_type=code&client_id=%s&redirect_uri=%s' % (client_id, redirect_uri)
return redirect(uri)
@app.route('/client/passport', methods=['POST', 'GET'])
def client_passport():
code = request.args.get('code')
uri = 'http://localhost:8888/oauth?grant_type=authorization_code&code=%s&redirect_uri=% s&client_id=%s' % (code, redirect_uri, client_id )
print('------------')
print(code)
print(uri)
return redirect(uri)
# return code
#資源服務(wù)器
@app.route('/test1', methods=['GET', 'POST'])
def test():
token = request.args.get('token')
print(token)
ret = verify_token(token)
if ret:
return json.dumps(ret)
else:
return 'error'
if __name__ == '__main__':
app.run(debug=True, port=8888)
'''
6.demo解析
對生成token和驗證token簡單的簡單說明位他,
生成token時:
生成token函數(shù)里的參數(shù)是一個字典氛濒,字典里有user_id 和授權(quán)碼,在此字典中加上一個隨機數(shù)salt和過期時間expires鹅髓,然后使用hmac生成簽名舞竿;生成簽名后將簽名與字典拼接,使用b64進行編碼
生成簽名:
生成簽名函數(shù)中窿冯,對傳來的數(shù)據(jù)骗奖,在加上密匙,然后生成簽名醒串,
驗證token:
首先對token進行base64解碼执桌,然后進行切割成原來的,字典與簽名芜赌,然后將字典部分進行生成簽名仰挣,生成后的簽名與之前切割出來的簽名進行對比驗證;驗證通過后缠沈,從切割中出來的字典里獲取過期時間膘壶,與當前時間對比,判斷是否過期
簡單舉例:
生成token
將數(shù)據(jù)A進行生成簽名為B洲愤,將A和B拼接后編碼颓芭,
驗證token
將token進行解碼,切割成A和B柬赐,將A進行生成簽名為C 亡问,將B和C進行對比驗證,驗證成功后從A中獲取過期時間與當前時間對比判斷是否過期躺率,
授權(quán)碼:
生成: 隨機生成一個數(shù)字(0玛界,10000)為鍵,uri和user_id為值保存為字典
驗證:從字典中獲取出uri悼吱,與之前oauth_redirect_uri保存的uri對比慎框,驗證。