Python Flask 扩展,用于使用带有 OAuth 的 Azure Active Directory 来保护应用程序
项目描述
Flask Azure AD OAuth 提供程序
用于使用 Azure Active Directory OAuth 保护应用程序的 Python Flask 扩展
目的
提供AuthLib 资源保护器/服务器,以使用 Flask 应用程序对用户和应用程序进行身份验证和授权,该应用程序具有Azure Active Directory提供的 OAuth 功能 ,作为 Microsoft 身份平台的一部分。
充当身份提供者的 Azure Active Directory 发出 OAuth 访问令牌,其声明由该提供者验证。这些声明包括用户和客户端应用程序的身份(用于身份验证),以及分配或委托给用户或应用程序(用于授权)的任何权限/范围。
此提供程序支持以下场景:
- 申请到申请
- 支持认证和授权
- 用于允许客户端应用程序访问另一个应用程序提供的某些功能或资源
- 可用于非交互式、机器对机器的进程(使用 OAuth 客户端凭据授予)
- 可选地,使用客户端应用程序的身份进行身份验证
- 可选地,使用直接分配给客户端应用程序的权限进行授权
- 用户到应用程序
- 支持认证和授权
- 用于允许用户访问其他应用程序提供的某些功能或资源
- 可用于交互式控制台(使用设备授权授权)或 Web 应用程序(使用 OAuth 授权代码授权)进程
- 使用用户的身份以及他们正在使用的客户端应用程序进行身份验证
- 可选地,使用分配给用户的权限、用户委托给客户端应用程序的权限和/或直接分配给客户端应用程序的权限以进行授权
其他方案可能有效,但不受官方支持,将来可能会改变。
注意:此提供程序不支持从 Azure 请求令牌的客户端应用程序。 如果需要,请参阅 Microsoft Authentication Library (MSAL) for Python包。
注意:此提供程序旨在解决英国南极调查局使用的应用程序的内部需求。它提供给其他人,希望对您的需求也有用,但是它没有(也不能)涵盖所有可用的选项。
安装
可以使用PyPi中的 Pip 安装此软件包:
$ pip install flask-azure-oauth
注意:从 0.6.0 版本开始,这个包需要 Flask 2.0 或更高版本。
用法
该提供程序提供了一个AuthLib 资源保护器,可用作 Flask 路由上的装饰器。
一个最小的应用程序如下所示:
from flask import Flask
from flask_azure_oauth import FlaskAzureOauth
app = Flask(__name__)
app.config['AZURE_OAUTH_TENANCY'] = 'xxx'
app.config['AZURE_OAUTH_APPLICATION_ID'] = 'xxx'
auth = FlaskAzureOauth()
auth.init_app(app)
@app.route('/unprotected')
def unprotected():
return 'hello world'
@app.route('/protected')
@auth()
def protected():
return 'hello authenticated entity'
@app.route('/protected-with-single-scope')
@auth('required-scope')
def protected_with_scope():
return 'hello authenticated and authorised entity'
@app.route('/protected-with-multiple-scopes')
@auth('required-scope1 required-scope2')
def protected_with_multiple_scopes():
return 'hello authenticated and authorised entity'
将路由限制到任何有效的用户或客户端应用程序(身份验证):
- 添加资源保护器作为装饰器(
auth
在本例中) - 例如/protected
路由
将路由限制为特定用户(授权):
- 将任何必需的范围添加到装饰器 - 例如
/projected-with-*
路由
独立于这些选项,可能需要特定的、受信任的客户端应用程序,而不管用户如何使用它们。这在用户可能被授权但客户端不能被信任的情况下很有用:
- 将
AZURE_OAUTH_CLIENT_APPLICATION_IDS
配置选项设置为 Azure 应用程序标识符列表
例如:
app.config['AZURE_OAUTH_CLIENT_APPLICATION_IDS'] = ['xxx']`
配置选项
资源保护器需要两个配置选项来正确验证令牌。这些是
通过方法从 Flask配置对象init_app()
中读取的。
配置选项 | 数据类型 | 必需的 | 描述 |
---|---|---|---|
AZURE_OAUTH_TENANCY |
力量 | 是的 | Azure AD 租户的 ID,所有应用程序和用户都在其中注册 |
AZURE_OAUTH_APPLICATION_ID |
力量 | 是的 | 受保护应用程序的 Azure AD 应用程序注册 ID |
AZURE_OAUTH_CLIENT_APPLICATION_IDS |
列表[字符串] | 不 | 授予对受保护应用程序访问权限的应用程序的 Azure AD 应用程序注册 ID |
注意:如果AZURE_OAUTH_CLIENT_APPLICATION_IDS
未设置该选项,则所有客户端应用程序都将受到信任,并且
azp
声明(如果存在)将被忽略。
在设置这些选项之前,您需要:
Flask 会话支持
此提供程序扩展了 AuthLib ResourceProtector 以支持检测存储在 Flask 会话中的访问令牌。
这适用于Authorization
无法轻松将标头设置为包含访问令牌的基于浏览器的应用程序。access_token
如果设置了会话密钥,将自动启用此支持。
访问令牌版本
从版本 0.5.0 开始,此提供程序与 Azure 访问令牌版本 1.0 和 2.0 兼容。在 0.5.0 版之前,只能使用 2.0 版令牌。有关令牌版本之间的差异,请参阅 Microsoft 的文档。
注意:如果您使用 1.0 版令牌,则此提供程序期望至少有一个identifierUris
属性值为api://{protected_application_id}
,其中{protected_application_id}
是表示受此提供程序保护的应用程序的应用程序注册的应用程序 ID。如果没有这个,您将收到无效受众的错误。
应用程序、用户、组和租户
Azure Active Directory 有许多不同的代理概念,代表受保护的事物以及希望与受保护事物交互的事物:
- 应用程序- 表示提供或希望使用应受限制的功能的服务:
- 提供功能的服务是受保护的应用程序,例如 API
- 希望以交互方式或非交互方式使用功能的服务是客户端应用程序:
- 交互式客户端应用程序包括例如自助服务门户
- 例如,非交互式客户端应用程序包括夜间同步任务
- 用户- 代表希望通过一个或多个客户端应用程序使用受保护应用程序提供的功能的个人(例如,用户可以使用自助服务门户访问信息)
- 组- 代表多个用户,以便于管理相似用户的权限(例如管理用户)
出于管理目的,所有代理都限定为 Azure 租户(可跨租户使用的用户除外)。
在 Azure 管理门户中:
- 应用程序由应用程序注册表示
- 用户由用户或可选的用户组表示
权限、角色和范围
Azure Active Directory 具有多种机制来控制代理如何相互交互:
- 角色- 赋予用户和/或组的功能、名称或标签(例如
admins
,staff
) - 直接权限- 受保护应用程序的功能客户端应用程序可以自行使用或无需当前用户的同意(例如机器对机器访问或修改来自所有用户的数据)
- 委托权限- 当前用户允许客户端应用程序使用的受保护应用程序的功能(例如,交互式访问或修改其数据)
一般来说,就 OAuth 生态系统而言,所有这些都可以视为 范围。如使用部分所述,范围可用于控制谁和/或什么可以使用受保护应用程序中的功能。
范围包括客户端应用程序生成的访问令牌(可能由用户以交互方式)并作为不记名令牌呈现给投影应用程序。Azure 在不同的声明中编码不同的机制:
roles
- 对于分配给用户的角色和直接分配给客户端应用程序的权限scp
- 对于用户委托给客户端应用程序的权限
为了便于使用,此扩展将这两个声明抽象为一组scopes
给定路线可能需要的声明。可能需要多个范围(作为逻辑 AND)以允许更灵活地使用范围。
在应用程序中定义权限和角色
权限和角色在 每个受保护应用程序的应用程序清单中定义。然后可以将它们分配给用户、组和客户端应用程序。
- 注册要保护的应用程序
- 向应用程序清单添加权限
例如:
"appRoles": [
{
"allowedMemberTypes": [
"Application"
],
"displayName": "List all Foo resources",
"id": "112b3a76-2dd0-4d09-9976-9f94b2ed965d",
"isEnabled": true,
"description": "Allows access to basic information for all Foo resources",
"value": "Foo.List.All"
}
],
为用户和应用程序分配权限和角色
权限和角色(统称为应用程序角色)通过 Azure 门户分配:
- 在受保护的应用程序中定义角色和权限
- 注册客户端应用程序
- 分配:
对于分配权限:
- 在当前用户同意的情况下,可以将权限委托给客户端应用程序
- 在租户管理员同意的情况下,权限可以直接分配给客户端应用程序
注意:非交互式应用程序需要直接分配,例如守护程序。
在 Azure 中注册应用程序
注意:这些说明既适用于受此提供程序保护的应用程序(受保护的应用程序),也适用于那些可能被用户授予使用此类应用程序的访问权限的应用程序(客户端应用程序)。
测试支持
对于测试应用程序,本地/测试 JSON Web 密钥集 (JWKS) 可用于签署本地/测试 JSON Web 令牌 (JWT),而无需依赖 Azure。本地令牌可以包括或不包括任意范围/角色,这可以确保此提供者正确执行特定范围的要求。
这需要使用由测试密钥签名的本地令牌,并修补FlaskAzureOauth._get_jwks
方法以使用相同的测试密钥验证令牌。
例如:
import unittest
from http import HTTPStatus
from unittest.mock import patch
from flask_azure_oauth import FlaskAzureOauth
from flask_azure_oauth.mocks.keys import TestJwk
from flask_azure_oauth.mocks.tokens import TestJwt
from examples import create_app
class AppTestCase(unittest.TestCase):
def setUp(self):
self.test_jwks = TestJwk()
with patch.object(FlaskAzureOauth, "_get_jwks") as mocked_get_jwks:
mocked_get_jwks.return_value = self.test_jwks.jwks()
# `self.app` should be set to a Flask application, either by direct import, or by calling an app factory
self.app = create_app()
self.app.config["TEST_JWKS"] = self.test_jwks
self.app_context = self.app.app_context()
self.app_context.push()
self.client = self.app.test_client()
def test_protected_route_with_multiple_scopes_authorised(self):
# Generate token with required roles
token = TestJwt(
app=self.app, roles=["BAS.MAGIC.ADD.Records.Publish.All", "BAS.MAGIC.ADD.Records.ReadWrite.All"]
)
# Make request to protected route with token
response = self.client.get(
"/protected-with-multiple-scopes", headers={"authorization": f"bearer { token.dumps() }"}
)
self.assertEqual(HTTPStatus.OK, response.status_code)
self.app_context.pop()
def test_protected_route_with_multiple_scopes_unauthorised(self):
# Generate token with no scopes
token = TestJwt(app=self.app)
# Make request to protected route with token
response = self.client.get(
"/protected-with-multiple-scopes", headers={"authorization": f"bearer { token.dumps() }"}
)
self.assertEqual(HTTPStatus.FORBIDDEN, response.status_code)
self.app_context.pop()
发展
该提供程序是作为 Python 库开发的。捆绑的 Flask 应用程序用于模拟其使用并充当运行测试等的框架。
开发环境
搭建本项目的本地开发环境需要Git 和Poetry 。
注意:如果您使用Pyenv,此项目会设置本地 Python 版本以保持一致性。
# clone from the BAS GitLab instance if possible
$ git clone https://gitlab.data.bas.ac.uk/web-apps/flask-extensions/flask-azure-oauth.git
# alternatively, clone from the GitHub mirror
$ git clone https://github.com/antarctica/flask-azure-oauth.git
# setup virtual environment
$ cd flask-azure-oauth
$ poetry install
代码风格
此项目必须使用 PEP-8 样式和格式指南,但 80 个字符的行限制除外。
黑色用于格式化、配置pyproject.toml
并作为
Python 代码 linting的一部分强制执行。
Black 可以与一系列编辑器集成,例如 PyCharm,以便在保存文件时自动应用格式。
要手动应用格式:
$ poetry run black src/ tests/
代码检查
Flake8和各种扩展用于 lint Python 文件。配置文件中记录了特定检查和任何配置选项./.flake8
。
手动检查文件:
$ poetry run flake8 src/ examples/
检查在持续集成中自动运行。
依赖项
该项目的 Python 依赖项使用Poetry in进行管理pyproject.toml
。
非代码文件,例如静态文件,也可以使用
in 键包含在Python 包中。include
pyproject.toml
添加新的依赖项
添加新的(开发)依赖项:
$ poetry add [dependency] (--dev)
然后更新用于 CI/CD 构建的 Docker 映像并推送到 BAS Docker Registry(由 GitLab 提供):
$ docker build -f gitlab-ci.Dockerfile -t docker-registry.data.bas.ac.uk/web-apps/flask-extensions/flask-azure-oauth:latest .
$ docker push docker-registry.data.bas.ac.uk/web-apps/flask-extensions/flask-azure-oauth:latest
更新依赖项
$ poetry update
请参阅上面的说明以更新 CI/CD 中使用的 Docker 映像。
依赖漏洞检查
安全包用于检查与已知漏洞的依赖关系。
重要的!与所有安全工具一样,安全性有助于发现常见错误,而不是保证代码安全。特别是使用免费的漏洞数据库,该数据库的更新频率低于付费选项。
这是发现漏洞方面容易实现的目标的好工具。它不能替代对依赖项的适当审查,或安全专业人员对潜在问题的适当审计。如果有任何疑问,您必须寻求适当的建议。
检查在持续集成中自动运行。
在本地检查:
$ poetry export --without-hashes -f requirements.txt | poetry run safety check --full-report --stdin
authlib
包裹
由于发布系列包含阻止
从 Jason Web Key (JWK) 实例访问声明的错误,因此authlib
依赖项已锁定到版本。这是一个已知问题,将在
版本中解决。有关更多信息,请参阅https://github.com/lepture/authlib/issues/314。0.14.3
0.15.x
kid
1.x
静态安全扫描
为了确保此 API 的安全性,源代码会针对Bandit进行检查, 并作为Python 代码 linting的一部分强制执行。
警告: Bandit 是一种静态分析工具,无法检查只有在运行应用程序时才能检测到的问题。与所有安全工具一样,Bandit 有助于发现常见错误,而不是安全代码的保证。
手动检查:
$ poetry run bandit -r src/ examples/
注意:这个包包含一些故意做不安全或无意义的事情的测试方法。这些是测试故障模式和错误处理所必需的,当按预期使用此包时,它们不是风险。这些变通办法已在适用的地方免除这些安全检查。
检查在持续集成中自动运行。
测试
集成测试
该项目使用集成测试来确保功能按预期工作并防止回归和漏洞。
Python UnitTest库用于使用 Flask 的测试框架运行测试。测试用例在其中的文件中定义,并在使用
开发环境中本地 Flask 应用程序中包含tests/
的 Flask CLI 命令时自动加载。test
要使用 PyCharm 手动运行测试,请使用包含的App(测试)运行/调试配置。
手动运行测试:
$ FLASK_APP=examples FLASK_ENV=testing poetry run python -m unittest discover
测试在持续集成中自动运行。
持续集成
所有提交都将使用 GitLab 的 CI/CD 平台触发持续集成过程,该平台配置为.gitlab-ci.yml
.
测试/示例应用
为了验证此提供程序是否适用于实际用例,测试 Flask 应用程序包含在
examples/__init__.py
. 此测试应用程序充当提供对受保护资源的访问和访问的应用程序。它可以使用在 BAS Web & Applications Test Azure AD 中注册的大量应用程序注册。
例如,这些应用程序允许测试不同版本的访问令牌。这些应用程序仅用于测试。它们不代表真实的应用程序,也不包含任何敏感或受保护的信息。
要将受保护资源的请求资源作为 API 进行测试,请设置适当的配置选项并运行应用程序容器:
$ FLASK_APP=examples poetry run flask
要将受保护资源中的资源请求作为浏览器应用程序进行测试,请设置适当的配置选项并启动应用程序容器:
$ FLASK_APP=examples poetry run flask run
Terraform 用于配置所使用的应用程序注册:
$ cd provisioning/terraform
$ docker-compose run terraform
$ az login --allow-no-subscriptions
$ terraform init
$ terraform validate
$ terraform apply
注意:应用程序注册资源中的几个属性需要在最初进行注册后进行设置(例如标识符)。这些需要在使用前注释掉。
某些属性(例如客户端机密)只能在应用程序在 Azure 门户中注册后才能设置。
Terraform 状态信息保存在 BAS Terraform Remote State 项目(内部)中。
部署
Python 包
该项目作为 Python 包分发,托管在PyPi中。
源代码和二进制包是使用 Poetry in Continuous Deployment自动构建和发布的。
注意:除了标记版本,CD 中内置的 Python 包将0.0.0
用作版本,以表明它们不是正式版本。
持续部署
使用 GitLab 的 CI/CD 平台的持续部署过程在.gitlab-ci.yml
.
发布程序
对于所有版本:
- 创建一个
release
分支 - 根据需要调整版本
pyproject.toml
- 关闭释放
CHANGELOG.md
- 推送更改,将
release
分支合并到main
中,并使用版本进行标记
该项目将通过持续部署自动构建并发布到 PyPi 。
反馈
该项目的维护者是 BAS Web & Applications Team,他们的联系方式是: servicedesk@bas.ac.uk。
问题跟踪
此项目使用问题跟踪,有关更多信息,请参阅 问题跟踪器。
注意:对此问题跟踪器的读写访问受到限制。联系项目维护人员以请求访问权限。
执照
版权所有 (c) 2019-2022 英国研究与创新 (UKRI),英国南极调查局。
特此免费授予任何人获得本软件和相关文档文件(“软件”)的副本,以不受限制地处理本软件,包括但不限于使用、复制、修改、合并的权利、发布、分发、再许可和/或出售本软件的副本,并允许向其提供本软件的人这样做,但须符合以下条件:
上述版权声明和本许可声明应包含在本软件的所有副本或大部分内容中。
本软件按“原样”提供,不提供任何形式的明示或暗示保证,包括但不限于适销性、特定用途适用性和非侵权保证。在任何情况下,作者或版权持有人均不对任何索赔、损害或其他责任承担任何责任,无论是在合同、侵权或其他方面,由本软件或本软件的使用或其他交易引起或与之相关。软件。
项目详情
下载文件
下载适用于您平台的文件。如果您不确定要选择哪个,请了解有关安装包的更多信息。
源分布
内置分布
flask_azure_oauth -0.7.0-py3-none-any.whl 的哈希值
算法 | 哈希摘要 | |
---|---|---|
SHA256 | bcc39ef55ae1a529e12039323896fcaccbe1251f141167be55663383c906b06a |
|
MD5 | a08fe5d4dec606530c1befcf8ba4cb56 |
|
布莱克2-256 | 2f06ce90986b9504e0c9f26b7860a5cd88c6464bea6392fa8e1ab87c487e3432 |