如何在Flask应用中通过服务账号密钥管控Data Studio报表访问
Looker Studio(原Data Studio)服务账号密钥管控访问方案
核心逻辑:关闭报表原生公网共享,所有报表访问请求先经过Flask后端做密钥校验,校验通过后才允许访问报表内容,服务账号仅分配最小必要权限,避免未授权访问。
前置GCP侧配置
- 新建专用服务账号,不要复用已有高权限账号,给该账号分配两类权限:
- BigQuery侧:绑定报表依赖数据集的
BigQuery Data Viewer、BigQuery Job User角色,保证账号能正常查询底表数据渲染报表 - Looker Studio侧:将目标报表共享给该服务账号,仅授予「查看者」权限,不要给编辑/管理权限
- BigQuery侧:绑定报表依赖数据集的
- 为该服务账号生成JSON格式密钥文件,存到Flask项目根目录,将文件名加入
.gitignore,避免密钥随代码泄露 - 调整报表共享设置:关闭公开链接共享,将访问范围设置为「仅被邀请的用户可访问」,从根源切断原生链接的未授权访问路径
后端逻辑改造
先安装依赖包:pip install flask google-auth requests
替换原有app.py代码,新增密钥校验装饰器、权限拦截逻辑、错误返回逻辑:
from flask import Flask, render_template, redirect, request, abort from google.oauth2 import service_account import requests from functools import wraps app = Flask(__name__) # 配置项,替换为自己的实际参数 VALID_ACCESS_KEYS = {"key_abc123", "key_def456"} # 可配置多个有效密钥,分配给不同用户 SA_KEY_PATH = "./service-account.json" # 服务账号JSON密钥的本地路径 REPORT_EMBED_URL = "你的Looker Studio报表嵌入地址" REPORT_SHARE_URL = "你的Looker Studio报表共享链接" def access_required(f): """权限校验装饰器,所有需要访问报表的路由都加这个装饰器""" @wraps(f) def decorated(*args, **kwargs): # 优先从请求头取密钥,兼容URL参数传参,生产环境建议只用请求头传密钥,避免密钥留存在浏览器历史 req_key = request.headers.get("X-Access-Key") or request.args.get("key") if not req_key or req_key not in VALID_ACCESS_KEYS: abort(403, "访问密钥无效,请联系管理员获取有效权限") return f(*args, **kwargs) return decorated @app.route('/') @access_required def index(): return render_template('index.html') @app.route('/report') @access_required def report_redirect(): # 校验通过后才跳转,可扩展为生成短时效签名链接,避免链接被转发后长期可用 return redirect(REPORT_SHARE_URL) @app.route('/embed-content') @access_required def embed_proxy(): """代理嵌入报表内容,不直接暴露原生报表地址""" creds = service_account.Credentials.from_service_account_file( SA_KEY_PATH, scopes=["https://www.googleapis.com/auth/lookerstudio.readonly"] ) creds.refresh(requests.Request()) return redirect(f"{REPORT_EMBED_URL}?access_token={creds.token}") @app.errorhandler(403) def handle_403(e): return render_template('403.html', err_info=e.description), 403 if __name__ == '__main__': app.run(host='127.0.0.1', port=8080, debug=True)
前端模板改造
- 修改
templates/index.html,替换原有硬编码的iframe地址,适配密钥透传逻辑:
<!DOCTYPE html> <html> <body> <h1>Datastudio Test</h1> <p>Datastudio Report</p> <button type="button" onclick="window.location.href=`/report?key=${new URLSearchParams(window.location.search).get('key')}`">打开独立报表页</button> <iframe width="600" height="800" src="/embed-content?key={{ request.args.get('key') }}" frameborder="0" style="border:0" allowfullscreen></iframe> </body> </html>
- 新增
templates/403.html作为无权限提示页:
<!DOCTYPE html> <html> <body> <h1>访问被拒绝</h1> <p>*{{ err_info }}*</p> </body> </html>
生产环境加固建议
- 不要把有效密钥硬编码在代码文件中,生产环境建议将密钥存在环境变量或专用密钥管理服务中,定期轮换
- 服务账号生成的访问令牌默认有效期为1小时,不要手动延长令牌时效,降低令牌泄露后的风险
- 安全要求较高的场景可以去掉
/report的直接跳转逻辑,所有报表内容通过/embed-content做服务端反向代理,用户端完全接触不到报表原生地址 - 增加访问日志,记录每个密钥对应的访问IP、访问时间,方便排查异常访问行为
注意:严格遵循最小权限原则,不要给服务账号分配报表所有者、BigQuery编辑等高权限,避免密钥泄露后数据被篡改。
内容的提问来源于stack exchange,提问作者Sammlona
相关产品推荐
相关产品推荐

