-
OAuth2.0認(rèn)證的四種模式
1.Authorization Code(授權(quán)碼模式)
2.Resource Owner Password Credentials(密碼模式)
3.Implicit(簡化授權(quán)碼模式)
4.Client Credentials(客戶端模式)
-
Resource Owner Password Credentials(密碼模式)
個(gè)人感覺密碼模式按照使用場景可以分為2類:
- 第一類,第三方登錄場景 比如A應(yīng)用是我創(chuàng)建的應(yīng)用,現(xiàn)在我們需要登錄A應(yīng)用,A應(yīng)用提供了可以使用B應(yīng)用的用戶名密碼登錄的接口,步驟如下:
1.用戶向A應(yīng)用提供B應(yīng)用的用戶名和密碼
2.A應(yīng)用拿著B應(yīng)用的用戶名和密碼向B應(yīng)用的授權(quán)服務(wù)器(Authorization Server)請求令牌(Token)
3.A應(yīng)用拿著令牌,就可以B應(yīng)用中允許范圍之內(nèi)的信息了
假設(shè)微信能用QQ號登錄
A應(yīng)用就是微信
B應(yīng)用就是QQ - 第二類,A應(yīng)用和B應(yīng)用屬于是同一個(gè)應(yīng)用的情況。認(rèn)證服務(wù)器和資源服務(wù)器屬于用一個(gè)服務(wù)器。
1.客戶端發(fā)起一個(gè)post請求,請求的類型是默認(rèn)類型,也就是contentType: 'application/x-www-form-urlencoded',請求參數(shù)中必須包含grant_type,username,password,如下:
......
$.ajax({
url: $this.data('href'),
type: "POST",
//http的basic校驗(yàn)
headers: {Authorization: 'Basic ' + base64_str},
contentType: 'application/x-www-form-urlencoded',
data: {grant_type: 'password', username: 'andy', password: '12345678'},
success: function (data) {
access_token = data.access_token;
token_type = data.token_type;
console.log('access_token=' + access_token)
},
error: function (data) {
console.log('data=' + data);
}
});
2.授權(quán)服務(wù)器(Flask)收到請求Token的請求后,做如下操作:做Http的Basic校驗(yàn),驗(yàn)證grant_type,username,password,根據(jù)username找到User對象,然后根據(jù)User對象生成access_token,使用Flask中的itsdangerous模塊可以非常方便的生成帶有效期的token(token的有效期一般為幾個(gè)小時(shí)),返回給客戶端
#根據(jù)授權(quán)請求,生成access_token
......
def post(self):
# 客戶端http basic校驗(yàn)/
username = request.authorization.username
password = request.authorization.password
# 對客戶端進(jìn)行http basic認(rèn)證
logging.debug('username=%s,password=%s', username, password)
# if (not (username == current_app.config['BASIC_HTTP_USERNAME'])
# or not (password == current_app.config['BASIC_HTTP_PASSWORD'])):
# return api_abort(code=401, message='Http basic認(rèn)證失敗')
grant_type = request.form.get('grant_type')
username = request.form.get('username')
password = request.form.get('password')
# 客戶端的請求進(jìn)行驗(yàn)證grant_type username password都需要驗(yàn)證
if grant_type is None or grant_type.lower() != 'password':
return api_abort(code=400, message='The grant type must be password.')
user = User.query.filter_by(username=username).first()
if not user or not user.validate_password(password):
return api_abort(400, message='用戶名或者密碼錯(cuò)誤')
# 生成token,返回給客戶端
token, expires_in = generate_token(user)
logging.debug('access_token=%s,expires_in=%s', token, expires_in)
response = jsonify({
'access_token': token,
'token_type': 'Bearer',
'expires_in': expires_in
})
# Cache-Control優(yōu)先級低于Pragma,但Pragma已經(jīng)逐步被拋棄
# 優(yōu)先級 Pragma>Cache-Control>Expires
# 禁用客戶端的緩存
response.headers['Cache-Control'] = 'no-store'
response.headers['Pragma'] = 'no-cache'
return response
......
# 注冊視圖
api_v1.add_url_rule('/oauth/token', view_func=AuthTokenAPI.as_view('token'), methods=['GET', 'POST'])
3.客戶端根據(jù)收到的access_token,請求資源服務(wù)器上的資源。
......
$.ajax({
type: 'GET',
headers: {Authorization: token_type + " " + access_token},
url: $this.data('href'),
success: function (data) {
console.log('data=' + JSON.stringify(data));
}
});
4.資源服務(wù)器校驗(yàn)token,如何校驗(yàn)token?
- 獲取token:
def get_token():
if 'Authorization' in request.headers:
try:
# 從header中按照空格截取,只截取一次
token_type, token = request.headers['Authorization'].split(None, 1)
logging.debug('token_type=%s,token=%s', token_type, token)
except ValueError:
token_type = token = None
else:
token_type = token = None
# 返回token和token的令牌(Bearer表示不記名令牌)
return token_type, token
- token校驗(yàn)
def validate_token(token):
s = TimedJSONWebSignatureSerializer(current_app.config['SECRET_KEY'])
try:
data = s.loads(token)
except BadSignature as b:
logging.debug(b.message)
return False
except SignatureExpired as s:
logging.debug(s.message)
return False
# token中取出用戶id,并進(jìn)而得到用戶對象
user = User.query.get(data['id'])
if not user:
# 沒有查到用戶,直接校驗(yàn)失敗
return False
# 綁定全局變量
g.current_user = user
return True
- 使用裝飾器,在每個(gè)視圖函數(shù)或類上調(diào)用
def auth_required(f):
@functools.wraps(f)
def decorated(*args, **kwargs):
token_type, token = get_token()
if not token_type or token_type.lower() != 'bearer':
return api_abort(400, 'token的類型必須是bearer。')
if not token:
# 校驗(yàn)token丟失
return token_missing()
# token是否合法
if not validate_token(token):
return invalid_token()
return f(*args, **kwargs)
return decorated
......
# 模擬服務(wù)器的資源
class UserAPI(MethodView):
# 調(diào)用token校驗(yàn)的裝飾器
decorators = [auth_required]
def get(self):
return jsonify(user_schema(g.current_user))
# 注冊視圖
api_v1.add_url_rule('/user', view_func=UserAPI.as_view('user'), methods=['GET'])
-
最后
請忽略拙劣的js代碼,僅僅為了實(shí)現(xiàn)思路,對于密碼模式是可以實(shí)現(xiàn)refresh_token功能的,看了好多資料,有大體上這幾個(gè)問題
1.refresh_token什么時(shí)候更新?
2.access_token過期后怎么處理?
3.refresh_token過期怎么處理?
4.使用flask-oauthlib能否能簡介一點(diǎn)?
歡迎討論:napoleno_1987@163.com