OAuth2.0認(rèn)證的密碼式認(rèn)證實(shí)現(xiàn)(Flask+Javascript)

  • 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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容