简体   繁体   English

跨多个 Flask RestPlus 端点文件共享 flask_httpauth 的 HTTPBasicAuth

[英]sharing flask_httpauth 's HTTPBasicAuth across multiple Flask RestPlus endpoint files

I have a project that has the following structure我有一个具有以下结构的项目

app.py
     |
     endpoints(directory)
     __init__.py
     endpoints_1.py
     endpoints_2.py
     auth_endppints.py
     auth_functionality_module.py
     etc ...

app.py has the following content (simplified) app.py 有以下内容(简体)


# Import modules
..... imports

# Set document root
ROOT = os.path.dirname(os.path.abspath(__file__))
path = Path(ROOT)
parent = path.parent.absolute()

# Change settings based on config
config = ConfigParser()
config_auth = ConfigParser()
config.read(vv_settings.CONFIG_DIR)
config_auth.read(str(parent) + "/configuration/auth.ini")

# Get Secret Key
SECRET_KEY = config_auth['secret']['secret_key']

# Database config
SQLALCHEMY_DATABASE_URI = '<URI built in the code>'

"""
Create a parser object locally
"""
parser = request_parser.parser

# initiate
application = Flask(__name__)
api.init_app(application)

# configure application
# By default, show all endpoints (collapsed)
application.config.SWAGGER_UI_DOC_EXPANSION = 'list'
application.config.from_object(__name__)
application.config['SECRET_KEY'] = SECRET_KEY
application.config['PROPAGATE_EXCEPTIONS'] = False
application.config['SQLALCHEMY_DATABASE_URI'] = SQLALCHEMY_DATABASE_URI
application.config['SQLALCHEMY_COMMIT_ON_TEARDOWN'] = True
application.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(application)


class User(db.Model):
    # Database model, currently very simple
    __tablename__ = 'authUsers'
    id = db.Column(db.Integer, primary_key=True)
    domain = db.Column(db.String(50), index=True)
    password_hash = db.Column(db.String(1000))
    email = db.Column(db.String(50))
    added = db.Column(db.Date)
    expires = db.Column(db.Date)
    termination = db.Column(db.Integer)

    def hash_password(self, password):
        """
        Code for password hashing
        The hash_password method takes a plain password as argument and stores a hash of it with the user.
        This method is called when a new user is registering with the server, or when the user changes the password.
        :param password: string
        :return: hashed password string
        """
        self.password_hash = pwd_context.encrypt(password)

    def verify_password(self, password):
        """
        The verify_password() method takes a plain password as argument and returns True if the password is
        correct or False if not.
        This method is called whenever the user provides credentials and they need to be validated.
        :param password:
        :return: True / False
        """
        verification = pwd_context.verify(password, self.password_hash)
        return verification

    # Method to generate token
    def generate_auth_token(self):
        s = Serializer(application.config['SECRET_KEY'])
        return s.dumps({'id': self.id})

    @staticmethod
    def verify_auth_token(token):
        s = Serializer(application.config['SECRET_KEY'])
        try:
            data = s.loads(token)
        except SignatureExpired:
            return None  # valid token, but expired
        except BadSignature:
            return None  # invalid token
        user = User.query.get(data['id'])
        return user


@application.before_request
def before_request():
    """
    Add the User object and the db to Flask g.
    :return: No return
    """
    g.User = User()
    g.db = db


"""
Representations
 - Adds a response-type into the "Response content type" drop-down menu displayed in Swagger
 - When selected, the APP will return the correct response-header and content type
 - The default for flask-RESTPlus is application/json
"""
@api.representation('application/xml')
def application_xml(data, code, headers):
    resp = representations.xml(data, code, headers)
    resp.headers['Content-Type'] = 'application/xml'
    return resp


@api.representation('application/json')
def application_json(data, code, headers):
    resp = representations.application_json(data, code, headers)
    resp.headers['Content-Type'] = 'application/json'
    return resp


"""
Error handlers
    - exceptions has now been imported from utils!
"""
def log_exception(exception_type):
    # We want to know the arguments passed and the path so we can replicate the error
    params = dict(request.args)
    params['path'] = request.path
    # Create the message and log
    message = '%s occurred at %s with params=%s' % (exception_type, time.ctime(), params)
    logger.exception(message, exc_info=True)


@application.errorhandler(exceptions.RemoteConnectionError)
def remote_connection_error_handler(e):
    # Add the Exception to the log ensuring that exc_info is True so that a traceback is also logged
    log_exception('RemoteConnectionError')

    # Collect Arguments
    args = parser.parse_args()
    if args['content-type'] != 'application/xml':
        return application_json({'message': str(e)},
                                504,
                                None)
    else:
        return application_xml({'message': str(e)},
                               504,
                               None)

if __name__ == '__main__':
    application.debug = True  
    application.run(host="127.0.0.1", port=5000)  

auth_endpoints.py contains the auth = HTTPBasicAuth(), also the @auth.verify_password decorated function and also imports the auth_functionality_module.py which contains the remaining apiAccess object found in this tutorial auth_endpoints.py 包含 auth = HTTPBasicAuth(),还有 @auth.verify_password 修饰函数,还导入了 auth_functionality_module.py,其中包含本教程中找到的剩余 apiAccess 对象

imports
...
from flask_httpauth import HTTPBasicAuth

"""
Auth settings
"""
# Reference
# https://blog.miguelgrinberg.com/post/restful-authentication-with-flask
acc = apiAccess.ApiAccess()
auth = HTTPBasicAuth()


# Password verification decorator
@auth.verify_password
def verify_password(username_or_token, password):
    """
    Verifies the passwod or token.
    This is the decorates auth method triggered by @auth.login_required
    :param username_or_token: string
    :param password: string
    :return: True / False
    """
    # first try to authenticate by token
    user = g.User.verify_auth_token(username_or_token)
    if not user:
        # try to authenticate with username/password
        user = g.User.query.filter_by(domain=username_or_token).first()
        if not user or not user.verify_password(password):
            return False
    g.user = user
    now = datetime.now().date()
    if now > user.expires:
        return False
    else:
        return True


"""
Namespace 1
"""
api1 = Namespace('auth', description='Endpoints for auth control, doc=False)

@api1.route("/token")
@api1.hide
class Token(Resource):
    @auth.login_required
    @api1.expect(parser, validate=True)
    def get(self):
        token = g.user.generate_auth_token()
        return representations.application_json({'your token': token.decode('ascii')}, 200, None)

"""
Other resources follow as does a second Namespace

The other endpoint.py files in the endpoints directory contain namespaces and endpoints which I want to add @auth.verify_password to. endpoints 目录中的其他 endpoint.py 文件包含我想要添加@auth.verify_password的命名空间和端点。 Is there any way to initialise auth有什么方法可以初始化身份验证

auth = HTTPBasicAuth

and make it accessible and functional to the other namespaces并使其对其他命名空间具有可访问性和功能性

What I have tried我试过的

  • importing and re-creating auth = HTTPBasicAuth in each file, but this does not seem to allow login在每个文件中导入并重新创建 auth = HTTPBasicAuth,但这似乎不允许登录
  • adding auth to Flask g.将身份验证添加到 Flask g。 also within @application.before_request which gives me RuntimeError: Working outside of application context也在@application.before_request中,这给了我RuntimeError: Working outside of application context

My ideal solution would be to have the auth = HTTPBasicAuth() in app.py and to be able to import it into the endpoint files.我理想的解决方案是在 app.py 中有auth = HTTPBasicAuth()并能够将其导入到端点文件中。 If anyone has done this and can provide code or a tutorial that would be great.如果有人这样做并且可以提供很棒的代码或教程。 I find it easier to solve these issues if I can see an example rather than just suggestions please.如果我能看到一个例子而不仅仅是建议,我发现解决这些问题更容易。 Thanks谢谢

The best way to organize this is (in my opinion) to move the auth object to its own module, let's say authentication.py :组织这个的最好方法是(在我看来)将auth对象移动到它自己的模块,比如说authentication.py

from flask_httpauth import HTTPBasicAuth

auth = HTTPBasicAuth()

@auth.verify_password
def verify_password(username_or_token, password):
    # ...

Then you can import the auth object in any of your other modules, where you need to add authentication to an endpoint.然后,您可以在需要向端点添加身份验证的任何其他模块中导入auth对象。 For example:例如:

from .authentication import auth

@app.route('/api/whatever')
@auth.login_required
def endpoint():
    # ...

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM