配置vNet集成后Azure存储GetBlob失败,Put/ListBlob正常求助
问题:Azure存储账户GetBlob请求403错误(vNet集成+服务端点配置场景)
我为Python应用配置了vNet集成,同时为存储账户配置了Service Endpoint并启用了vNet防火墙。PutBlob和ListBlob请求可成功执行,但GetBlob请求失败,无法在测试应用中查看图片。
存储日志详情
2.0;2024-07-11T11:32:44.8196257Z;ListBlobs;Success;200;9;6;authenticated;storagecasbx01we;storagecasbx01we;blob;"https:///<endpoint>.blob.core.windows.net:443/cp-images?restype=container&comp=list";"/<endpoint>/cp-images";67d5b517-701e-002a-5086-d3f759000000;0;10.0.0.254:52420;2020-06-12;575;0;220;2956;0;;;;;"azsdk-python-storage-blob/12.8.1 Python/3.12.2 (Linux-5.15.153.1-2.cm2-x86_64-with-glibc2.31)";;"4a9a9e1e-3f79-11ef-b188-a665be233668";;;;;;;;; 2.0;2024-07-11T11:32:51.9590052Z;PutBlob;BlobAlreadyExists;409;5;5;authenticated;storagecasbx01we;storagecasbx01we;blob;"https://<endpoint>.blob.core.windows.net:443/cp-images/NewSmall.png";"/<endpoint>/cp-images/NewSmall.png";67d5cab5-701e-002a-5f86-d3f759000000;0;10.0.0.254:52420;2020-06-12;659;3966;235;220;3966;;"FUj7LV1B0HHctq3t5gmWCA== ";;;;"If-None-Match=*";"azsdk-python-storage-blob/12.8.1 Python/3.12.2 (Linux-5.15.153.1-2.cm2-x86_64-with-glibc2.31)";;"4eeaa860-3f79-11ef-b188-a665be233668";;;;;;;;; 2.0;2024-07-11T11:32:52.1686959Z;ListBlobs;Success;200;6;3;authenticated;storagecasbx01we;storagecasbx01we;blob;"https:///<endpoint>.blob.core.windows.net:443/cp-images?restype=container&comp=list";"/<endpoint>/cp-images";67d5cb4f-701e-002a-6786-d3f759000000;0;10.0.0.254:52420;2020-06-12;575;0;220;2956;0;;;;;"azsdk-python-storage-blob/12.8.1 Python/3.12.2 (Linux-5.15.153.1-2.cm2-x86_64-with-glibc2.31)";;"4f061460-3f79-11ef-b188-a665be233668";;;;;;;;; 2.0;2024-07-11T11:33:00.5570855Z;ListBlobs;Success;200;7;2;authenticated;storagecasbx01we;storagecasbx01we;blob;"https:///<endpoint>.blob.core.windows.net:443/cp-images?restype=container&comp=list";"/storagecasbx01we/cp-images";67d5e450-701e-002a-2286-d3f759000000;0;10.0.0.254:52420;2020-06-12;575;0;220;2956;0;;;;;"azsdk-python-storage-blob/12.8.1 Python/3.12.2 (Linux-5.15.153.1-2.cm2-x86_64-with-glibc2.31)";;"540c6748-3f79-11ef-b188-a665be233668";;;;;;;;; 2.0;2024-07-11T11:32:45.3248984Z;GetBlob;AnonymousIpAuthorizationError;403;4;4;anonymous;;storagecasbx01we;blob;"https:///<endpoint>.blob.core.windows.net:443/cp-images/images.png";"/";1392860f-f01e-000b-4186-d3d322000000;0;163.116.166.103:8762;2009-09-19;624;0;105;246;0;;;;;"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36";"https://<app_service>.azurewebsites.net/";;;;;;;;;; 2.0;2024-07-
Python代码片段
@app.route("/") def view_photos(): blob_items = container_client.list_blobs() # list all the blobs in the container img_html = "<div style='display: flex; justify-content: space-between; flex-wrap: wrap;'>" for blob in blob_items: blob_client = container_client.get_blob_client(blob=blob.name) # get blob client to interact with the blob and get blob url img_html += "<img src='{}' width='auto' height='200' style='margin: 0.5em 0;'/>".format(blob_client.url) # get the blob url and append it to the html img_html += "</div>" # return the html with the images return """ <head> <!-- CSS only --> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous"> </head> <body> <nav class="navbar navbar-expand-lg navbar-dark bg-primary"> <div class="container"> <a class="navbar-brand" href="/">Photos App</a> </div> </nav> <div class="container"> <div class="card" style="margin: 1em 0; padding: 1em 0 0 0; align-items: center;"> <h3>Upload new File</h3> <div class="form-group"> <form method="post" action="/upload-photos" enctype="multipart/form-data"> <div style="display: flex;"> <input type="file" accept=".png, .jpeg, .jpg, .gif" name="photos" multiple class="form-control" style="margin-right: 1em;"> <input type="submit" class="btn btn-primary"> </div> </form> </div> </div> """ + img_html + "</div></body>"
问题分析
从日志可见:
- PutBlob和ListBlob请求来自App Service的私有IP(10.0.0.254),且已通过认证,能正常访问存储账户
- GetBlob请求来自客户端浏览器的公网IP(163.116.166.103),属于匿名访问,被存储账户的vNet防火墙拦截,触发
AnonymousIpAuthorizationError导致403
根本原因是当前代码直接将blob的原始URL返回给前端,浏览器会绕过App Service,直接向存储账户发起请求,无法利用App Service的vNet集成通道。
解决方案
选项1:生成SAS令牌(推荐)
为每个blob生成带有效期的SAS令牌,将包含SAS的URL返回给前端。这样浏览器即使从公网发起请求,也能通过SAS令牌完成认证,无需修改防火墙配置。
修改代码中的blob URL生成逻辑:
from azure.storage.blob import generate_blob_sas, BlobSasPermissions from datetime import datetime, timedelta @app.route("/") def view_photos(): blob_items = container_client.list_blobs() img_html = "<div style='display: flex; justify-content: space-between; flex-wrap: wrap;'>" for blob in blob_items: blob_client = container_client.get_blob_client(blob=blob.name) # 生成SAS令牌,有效期1小时,仅授予读权限 sas_token = generate_blob_sas( account_name=container_client.account_name, container_name=container_client.container_name, blob_name=blob.name, account_key=container_client.credential.account_key, permission=BlobSasPermissions(read=True), expiry=datetime.utcnow() + timedelta(hours=1) ) blob_url_with_sas = f"{blob_client.url}?{sas_token}" img_html += "<img src='{}' width='auto' height='200' style='margin: 0.5em 0;'/>".format(blob_url_with_sas) img_html += "</div>" # 其余代码保持不变
选项2:通过App Service代理Blob请求
新增一个路由,让App Service作为中间层,从存储账户获取blob内容后返回给前端。所有请求都走App Service的vNet集成通道,保持存储账户的私有访问特性。
示例代码:
@app.route("/blob/<path:blob_name>") def serve_blob(blob_name): blob_client = container_client.get_blob_client(blob=blob_name) blob_data = blob_client.download_blob().readall() # 根据文件扩展名动态设置Content-Type content_type = "image/png" if blob_name.endswith(".jpg") or blob_name.endswith(".jpeg"): content_type = "image/jpeg" elif blob_name.endswith(".gif"): content_type = "image/gif" return blob_data, 200, {"Content-Type": content_type} # 修改view_photos中的img标签src为代理路由 @app.route("/") def view_photos(): blob_items = container_client.list_blobs() img_html = "<div style='display: flex; justify-content: space-between; flex-wrap: wrap;'>" for blob in blob_items: img_html += "<img src='/blob/{}' width='auto' height='200' style='margin: 0.5em 0;'/>".format(blob.name) img_html += "</div>" # 其余代码保持不变
选项3:允许客户端公网IP(不推荐)
在存储账户的防火墙规则中添加客户端的公网IP地址。此方法会降低存储账户的安全性,且客户端IP变化时需要手动更新防火墙配置,仅适合临时测试场景。
内容的提问来源于stack exchange,提问作者Andrew Striletskyi
相关产品推荐
相关产品推荐

