flask HTTPAuth封装:同时支持Token和Basic认证的解决方案

背景

项目通过Flask构建的服务端需要增加用户认证功能,而服务端除了为前端页面提供接口以外,还需要开放API接口供其他系统调用,同样的新增的用户认证功能也要同时适配这两种应用场景。

需求分析

  • 前端页面为了保证访问页面的流畅以及浏览器兼容性,最好是使用token或session做用户认证,因为前端有跨域需求,调用cookie比较麻烦,因此这里直接使用token。
  • 而在API接口调用的需求中,使用basic认证会比较方便,只需要在请求头附带账号密码即可,如果使用token验证方式使用API接口的话,则需要先从认证端口获取一个token,再在后续的api请求中附带这个token,对接方面会增加复杂度。
  • 从flask_httpauth库中可以看到flask封装好的几种认证方式:Basic认证、Digest认证、Token认证等,通过继承对应的这几个类即可实现这几种认证方式,但由于不同认证方式是通过scheme这个参数配置的,因此不存在能同时适配多种认证方式的封装,需要自行开发;
    在这里插入图片描述

代码

以下是基于HTTPAuth类进行的封装,通过重写authenticate、get_auth这两个类,在收到认证请求时根据请求头(Basic Auth认证对应请求头为Basic,Token认证对应请求头为Bearer)选择对应的解析以及验证方式,从而实现了对两种认证请求的同时支持。

from itsdangerous import TimedJSONWebSignatureSerializer as Serializer, BadSignature, SignatureExpired
from flask import make_response, jsonify
from flask import make_response, jsonify, session, request
from flask_httpauth import HTTPAuth
from werkzeug.datastructures import Authorization
from base64 import b64decode,b64encode

class AuthBase(HTTPAuth):
    def __init__(self, *args,**kwargs):
        super().__init__(*args,**kwargs)
        self.verify_password_callback = None
        self.verify_token_callback = None
        self.auth_verify()

    def verify_password(self, f):
        self.verify_password_callback = f
        return f

    def verify_token(self, f):
        self.verify_token_callback = f
        return f

    def authenticate(self, auth, stored_password):
        if auth:
            if auth.type.upper() == 'BASIC' :
                if auth:
                    username = auth.username
                    client_password = auth.password
                else:
                    username = ""
                    client_password = ""
                if self.verify_password_callback:
                    return self.ensure_sync(self.verify_password_callback)(
                        username, client_password)
            elif auth.type.upper() == 'BEARER' :
                token = auth['token']
                if self.verify_token_callback:
                    return self.ensure_sync(self.verify_token_callback)(token)
        else:
            token = ""
            if self.verify_token_callback:
                    return self.ensure_sync(self.verify_token_callback)(token)

    def get_auth(self):
        auth = None
        if self.header is None or self.header == 'Authorization':
            auth = request.authorization
            if auth is None and 'Authorization' in request.headers:
                try:
                    header = request.headers['Authorization'].encode('utf-8')
                    auth_type = header.split(b' ', 1)[0].decode('utf-8')
                    if auth_type.upper() == 'BASIC' :
                        credentials = header.split(b' ', 1)[1]
                        encoded_username, encoded_password = b64decode(credentials).split(b':', 1)
                        try:
                            username = encoded_username.decode('utf-8')
                            password = encoded_password.decode('utf-8')
                        except UnicodeDecodeError:
                            # try to decode again with latin-1, which should always work
                            username = encoded_username.decode('latin1')
                            password = encoded_password.decode('latin1')
                        auth = Authorization(auth_type, {'username': username, 'password': password})
                    elif auth_type.upper() == 'BEARER' :
                        token = header.split(b' ', 1)[1]
                        auth = Authorization(auth_type, {'token': token})
                except (ValueError, KeyError):
                    pass
        elif self.header in request.headers:
            auth = Authorization(self.scheme,{'token': request.headers[self.header]})
        if auth is not None and auth.type.upper() not in ['BASIC','BEARER']:
            auth = None
        return auth

使用方式上和flask原生认证类一样,通过被@verify_token、@verify_password装饰器装饰的函数实现Basic、Token认证逻辑,@login_required装饰器实现认证保护。
以下是一个参考示例:

class AuthToken(AuthBase):
    def __init__(self, *args,**kwargs):
        super().__init__(*args,**kwargs)
        self.auth_token()

    def auth_token(self):
        @self.verify_password
        def __verify_password(account,passwd):
            return_value = None
            if account == "":
                return None
            else :
                FlaskMsg(f">>> BASIC验证: 用户[{account}]")
            try :
                pwd = db.query_pwd(account)
                if passwd == pwd : 
                    FlaskMsg(f">>> BASIC验证: 用户[{account}]验证通过")
                    return_value = account
            except Exception as e:
                FlaskMsg(f">>> BASIC验证失败\n异常信息:{e}")
                return_value = None
            finally: 
                return return_value

        @self.verify_token
        def verify_token(token):
            form_data = unpack_auth_token(token)
            if form_data == False : 
                return False
            elif 'account' in form_data:
                return True
            else :
                return False
                
def generate_auth_token(form_data):
    s = Serializer(SECRET_KEY, expires_in=TOKEN_EXPIRATION)
    return s.dumps(form_data)

def unpack_auth_token(token):
    s = Serializer(SECRET_KEY, expires_in=TOKEN_EXPIRATION)
    try:
        form_data = s.loads(token)
    except SignatureExpired:
        FlaskMsg(f">>> Token已过期,当前Token刷新时间[{TOKEN_EXPIRATION}]s")
        return False
    except BadSignature:
        FlaskMsg(f">>> Token校验不通过,Token: [{token}]")
        return False
    return form_data

验证

  • Basic Auth认证:Postman在Authorization中设置Basic Auth用户名密码后,可以调用api端口返回数据
    在这里插入图片描述
  • Token认证:Postman在Authorization中设置Token为从认证接口获取的Token后,同样可以调用api端口返回数据
    在这里插入图片描述
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值