如何在Python/Flask Web应用中通过Azure App Service Auth基于用户权限访问Blob
问题核心分析
你当前的问题是:使用App Service系统托管身份调用Blob存储时,所有操作都以应用身份执行,而非登录用户的身份,导致无法基于用户权限控制容器访问和Blob上传。核心解决思路是获取登录用户的Azure AD访问令牌,用该令牌作为凭据调用Blob存储API,而非依赖托管身份。
以下是具体实现方案:
方案1:利用App Service Easy Auth(已启用的身份验证组件)获取用户令牌
这是最适合你现有场景的方案,无需额外引入复杂的身份验证库,直接复用App Service自带的Easy Auth能力:
步骤说明
- 确认Easy Auth配置:确保已限制企业用户登录,且在身份验证设置中,将「允许的令牌受众」添加为Blob存储的资源ID:
https://storage.azure.com(确保返回的访问令牌可用于Blob存储API)。 - 获取用户访问令牌:用户登录后,Easy Auth会自动在请求头注入
X-MS-TOKEN-AAD-ACCESS-TOKEN;也可通过请求/.auth/me端点解析用户身份信息及令牌。 - 用用户令牌构建BlobServiceClient:以用户令牌为凭据创建客户端,此时所有Blob操作都会遵循该用户的RBAC权限。
Flask代码示例
from flask import request from azure.storage.blob import BlobServiceClient # 初始化Flask应用(省略已有代码) def get_user_blob_client(): # 从请求头获取用户的AAD访问令牌 user_token = request.headers.get("X-MS-TOKEN-AAD-ACCESS-TOKEN") if not user_token: return None # 用用户令牌创建BlobServiceClient return BlobServiceClient( account_url="https://<你的存储账户名>.blob.core.windows.net", credential=user_token ) # 示例:列出用户有权限访问的容器 @app.route("/containers") def list_user_containers(): blob_client = get_user_blob_client() if not blob_client: return "未授权", 401 containers = blob_client.list_containers() return [container.name for container in containers] # 示例:上传Blob到用户有权限的容器 @app.route("/upload/<container_name>", methods=["POST"]) def upload_blob(container_name): blob_client = get_user_blob_client() if not blob_client: return "未授权", 401 blob_container_client = blob_client.get_container_client(container_name) # 从请求中获取文件对象(需根据你的前端上传逻辑调整) file = request.files["file"] blob_container_client.upload_blob(name=file.filename, data=file) return "上传成功", 200
方案2:用MSAL Python库自定义身份验证流程
如果需要更灵活的登录控制(比如自定义登录页面、令牌刷新逻辑),可直接集成MSAL库:
步骤说明
- 注册AAD客户端应用:在Azure AD中注册应用,配置重定向URI(例如
https://<你的App Service域名>/authorized),添加「Azure Storage」的user_impersonation委托权限,并完成管理员同意。 - 集成MSAL实现登录流程:在Flask中实现登录跳转、令牌获取逻辑。
- 用用户令牌调用Blob存储:获取用户令牌后,以其为凭据构建BlobServiceClient。
Flask代码示例
import msal from flask import Flask, redirect, request, session, url_for app = Flask(__name__) app.secret_key = "你的Flask密钥" # MSAL配置 CLIENT_ID = "<你的AAD客户端ID>" CLIENT_SECRET = "<你的AAD客户端密钥>" AUTHORITY = "https://login.microsoftonline.com/<你的租户ID>" # Blob存储的权限范围 SCOPE = ["https://storage.azure.com/.default"] @app.route("/login") def login(): msal_client = msal.ConfidentialClientApplication( CLIENT_ID, authority=AUTHORITY, client_credential=CLIENT_SECRET ) # 生成授权URL auth_url = msal_client.get_authorization_request_url( SCOPE, redirect_uri=url_for("authorized", _external=True) ) return redirect(auth_url) @app.route("/authorized") def authorized(): code = request.args.get("code") msal_client = msal.ConfidentialClientApplication( CLIENT_ID, authority=AUTHORITY, client_credential=CLIENT_SECRET ) # 通过授权码获取用户令牌 result = msal_client.acquire_token_by_authorization_code( code, SCOPE, redirect_uri=url_for("authorized", _external=True) ) if "access_token" in result: session["user_token"] = result["access_token"] return redirect("/containers") return "登录失败", 401 @app.route("/containers") def list_containers(): user_token = session.get("user_token") if not user_token: return redirect("/login") blob_client = BlobServiceClient( account_url="https://<你的存储账户名>.blob.core.windows.net", credential=user_token ) containers = blob_client.list_containers() return [container.name for container in containers]
关于「双应用注册+user_impersonation」的逻辑解释
这个方案主要用于自定义API场景,而非Blob存储这类微软原生服务:
- 「资源应用」:代表你的自定义后端API,需定义
user_impersonation权限(允许客户端应用以用户身份访问该API)。 - 「客户端应用」:你的Flask应用,需申请访问资源应用的
user_impersonation权限。 - 流程:用户登录客户端应用后,客户端获取带有
user_impersonation权限的令牌,用该令牌调用资源API,此时资源API会识别用户身份并控制权限。
对于Blob存储,微软已提供现成的资源应用(https://storage.azure.com),无需额外注册资源应用,直接使用其user_impersonation权限即可,也就是上述两个方案中用到的权限范围。
关键注意事项
- RBAC权限配置:必须给登录用户分配Blob存储的RBAC角色(如「存储Blob数据读者」「存储Blob数据参与者」),而非仅给托管身份分配权限。
- 令牌过期处理:用户访问令牌有过期时间(默认1小时),用Easy Auth时可通过
/.auth/refresh端点刷新;用MSAL时可调用acquire_token_silent自动刷新令牌。 - 托管身份的用途:如果应用需要执行用户无关的后台操作(如定时清理Blob),可保留托管身份的权限,但用户操作必须用用户令牌。
内容的提问来源于stack exchange,提问作者Chris W
相关产品推荐
相关产品推荐

