Poznámka:
Přístup k této stránce vyžaduje autorizaci. Můžete se zkusit přihlásit nebo změnit adresáře.
Přístup k této stránce vyžaduje autorizaci. Můžete zkusit změnit adresáře.
Důležité
Tato funkce je ve verzi Public Preview.
Tato stránka popisuje, jak funguje vkládání externích uživatelů, jak nakonfigurovat pracovní prostor Azure Databricks pro zabezpečené sdílení vložených řídicích panelů a jak začít používat ukázkové aplikace. Vkládání externích uživatelů používá instanční objekt a přístupové tokeny s vymezeným oborem k ověřování a autorizaci přístupu k vloženým řídicím panelům. Tento přístup umožňuje sdílet řídicí panely s diváky mimo vaši organizaci, jako jsou partneři a zákazníci, aniž byste pro tyto uživatele zřídili účty Azure Databricks.
Další možnosti vkládání, včetně vkládání řídicích panelů pro uživatele ve vaší organizaci, najdete v tématu Vložení řídicího panelu.
Jak funguje vkládání pro externí uživatele
Postup diagramu a číslovaný postup vysvětlující, jak se uživatelé ověřují, a řídicí panely se při vkládání řídicího panelu pro externí uživatele naplní výsledky s vymezeným uživatelem.
- Ověřování a žádost uživatele: Uživatel se přihlásí k vaší aplikaci. Front-end vaší aplikace odešle na server ověřený požadavek na přístupový token řídicího panelu.
-
Ověřování instančního objektu: Váš server používá tajný klíč instančního objektu k vyžádání a přijetí tokenu OAuth ze serveru Databricks. Jedná se o široce vymezený token, který může volat všechna rozhraní API řídicího panelu, ke kterým má Azure Databricks přístup jménem instančního objektu. Váš server volá
/tokeninfokoncový bod pomocí tohoto tokenu a předává základní informace o uživateli, napříkladexternal_viewer_idaexternal_value. Zobrazení bezpečné prezentace řídicích panelů jednotlivým uživatelům -
Generování tokenů v oboru uživatele: Pomocí odpovědi z koncového
/tokeninfobodu a koncového bodu Databricks OpenID Connect (OIDC) váš server vygeneruje nový pevně vymezený token, který kóduje informace o uživateli, které jste předali. -
Vykreslování řídicího panelu a filtrování dat: Stránka aplikace vytvoří instanci
DatabricksDashboard@databricks/aibi-clienttokenu v oboru uživatele a předá ho během sestavování. Řídicí panel se vykreslí s kontextem uživatele. Tento token autorizuje přístup, podporuje auditování aexternal_viewer_idprovádíexternal_valuefiltrování dat. Dotazy v datových sadách řídicích panelů můžou odkazovat__aibi_external_valuena použití filtrů pro jednotlivé uživatele a zajistit tak, aby každý prohlížeč viděl jenom data, která smí zobrazit.
Bezpečné prezentování řídicích panelů jednotlivým uživatelům
Nakonfigurujte aplikační server tak, aby vygeneroval jedinečný token v oboru uživatele pro každého uživatele na základě jejich external_viewer_id. Díky tomu můžete sledovat zobrazení řídicích panelů a využití prostřednictvím protokolů auditu. Spáruje external_viewer_id se s proměnnou external_value, která funguje jako globální proměnná, která se dá vložit do dotazů SQL používaných v datových sadách řídicích panelů. To umožňuje filtrovat data zobrazená na řídicím panelu pro každého uživatele.
external_viewer_id se předává do protokolů auditu řídicího panelu a nesmí obsahovat identifikovatelné osobní údaje. Tato hodnota by měla být také jedinečná pro jednotlivé uživatele.
external_value se používá při zpracování dotazů a může obsahovat identifikovatelné osobní údaje.
Následující příklad ukazuje, jak použít externí hodnotu jako filtr v dotazech datové sady:
SELECT *
FROM sales
WHERE region = __aibi_external_value
Přehled nastavení
Tato část obsahuje základní koncepční přehled kroků, které je potřeba provést, abyste mohli řídicí panel vložit do externího umístění.
Pokud chcete vložit řídicí panel do externí aplikace, nejprve vytvoříte instanční objekt v Azure Databricks a vygenerujete tajný klíč. Instančnímu objektu musí být udělen přístup pro čtení k řídicímu panelu a jeho podkladovým datům. Váš server používá tajný kód instančního objektu k načtení tokenu, který má přístup k rozhraním API řídicího panelu jménem instančního objektu. Pomocí tohoto tokenu /tokeninfo server volá koncový bod rozhraní API, koncový bod OpenID Connect (OIDC), který vrací základní informace o profilu uživatele, včetně external_value hodnot a external_viewer_id hodnot. Tyto hodnoty umožňují přidružit žádosti k jednotlivým uživatelům.
Pomocí tokenu získaného z instančního objektu vygeneruje váš server nový token s vymezeným oborem pro konkrétního uživatele, který přistupuje k řídicímu panelu. Tento token s oborem uživatele se předává na stránku aplikace, kde aplikace vytvoří DatabricksDashboard instanci objektu @databricks/aibi-client z knihovny. Token obsahuje informace specifické pro uživatele, které podporují auditování a vynucují filtrování, aby každý uživatel viděl pouze data, ke kterým má oprávnění přistupovat. Z pohledu uživatele poskytuje přihlášení k aplikaci automaticky přístup k vloženého řídicího panelu se správnou viditelností dat.
Aspekty omezení rychlosti a výkonu
Externí vkládání má limit rychlosti 20 načtení řídicího panelu za sekundu. Můžete otevřít více než 20 řídicích panelů najednou, ale ne více než 20 může začít načítat současně.
Požadavky
Pokud chcete implementovat externí vkládání, ujistěte se, že splňujete následující požadavky:
- Na publikovaném řídicím panelu musíte mít aspoň oprávnění SPRAVOVAT. Viz Kurz: Použití ukázkových řídicích panelů k rychlému vytvoření a publikování ukázkového řídicího panelu v případě potřeby
- Musíte mít nainstalované Rozhraní příkazového řádku Databricks verze 0.205 nebo novější. Pokyny najdete v tématu Instalace nebo aktualizace rozhraní příkazového řádku Databricks . Informace o konfiguraci a používání ověřování OAuth najdete v tématu Ověřování uživatele na počítači (U2M).
- Správce pracovního prostoru musí definovat seznam schválených domén, které můžou hostovat vložený řídicí panel. Pokyny najdete v tématu Správa vkládání řídicího panelu .
- Externí aplikace pro hostování vloženého řídicího panelu Můžete použít vlastní aplikaci nebo použít poskytnuté ukázkové aplikace.
Krok 1: Vytvoření instančního objektu
Vytvořte instanční objekt, který bude fungovat jako identita vaší externí aplikace v rámci Azure Databricks. Tento instanční objekt ověřuje požadavky jménem vaší aplikace.
Vytvoření instančního objektu:
- Jako správce pracovního prostoru se přihlaste k pracovnímu prostoru Azure Databricks.
- Klikněte na své uživatelské jméno v horním panelu pracovního prostoru Azure Databricks a vyberte Nastavení.
- V levém podokně klikněte na Identita a přístup .
- Vedle poskytovatelů služeb klikněte na Spravovat.
- Klikněte na Přidat instanční objekt.
- Klikněte na Přidat nový.
- Zadejte popisný název instančního objektu.
- Klikněte na tlačítko Přidat.
- Otevřete instanční objekt, který jste právě vytvořili ze stránky výpisu instančních objektů . V případě potřeby ho vyhledejte podle názvu pomocí pole Filtrovat textové položky.
- Na stránce podrobností instančního objektu si poznamenejte ID aplikace. Ověřte, že jsou zaškrtnutá políčka přístup k Sql Databricks a přístup k pracovnímu prostoru .
Krok 2: Vytvoření tajného klíče OAuth
Vygenerujte tajný kód instančního objektu a shromážděte následující hodnoty konfigurace, které budete potřebovat pro vaši externí aplikaci:
- ID instančního objektu (klienta)
- Klientské tajemství
Instanční objekt používá tajný klíč OAuth k ověření své identity při vyžádání přístupového tokenu z externí aplikace.
Vygenerování tajného kódu:
- Na stránce podrobností instančního objektu klikněte na Tajné kódy.
- Klikněte na Vygenerovat tajný klíč.
- Zadejte hodnotu životnosti nového tajného kódu ve dnech (např. mezi 1 a 730 dny).
- Zkopírujte tajný kód okamžitě. Jakmile opustíte tuto obrazovku, nemůžete tento tajný kód znovu zobrazit.
Krok 3: Přiřazení oprávnění k instančnímu objektu
Instanční objekt, který jste vytvořili, funguje jako identita, která poskytuje přístup k řídicímu panelu prostřednictvím vaší aplikace. Jeho oprávnění platí jenom v případě, že řídicí panel není publikovaný s oprávněními ke sdíleným datům. Pokud se použijí oprávnění ke sdíleným datům, přihlašovací údaje vydavatele přistupují k datům. Další podrobnosti a doporučení najdete v tématu Přístupy k ověřování vkládání.
- Kliknutím na Řídicí panely na bočním panelu pracovního prostoru otevřete stránku výpisu řídicího panelu.
- Klikněte na název řídicího panelu, který chcete vložit. Otevře se publikovaný řídicí panel.
- Klikněte na Share (Sdílet).
- Pomocí textového pole v dialogovém okně Sdílení vyhledejte instanční objekt a klikněte na něj. Nastavte úroveň oprávnění na CAN RUN. Potom klikněte na Přidat.
- Poznamenejte si ID řídicího panelu. ID řídicího panelu najdete v adrese URL řídicího panelu (např
https://<your-workspace-url>/dashboards/<dashboard-id>. ). Podrobnosti o pracovním prostoru Databricks
Poznámka:
Pokud publikujete řídicí panel s individuálními oprávněními k datům, musíte služebnímu principálovi udělit přístup k datům použitým v řídicím panelu. Výpočetní přístup vždy používá přihlašovací údaje vydavatele, takže nemusíte udělovat výpočetní oprávnění instančnímu objektu.
Aby mohl instanční objekt číst a zobrazovat data, musí mít alespoň SELECT oprávnění k tabulkám a zobrazením odkazovaným na řídicím panelu. Podívejte se, kdo může spravovat oprávnění?
Krok 4: Použití ukázkové aplikace k ověřování a generování tokenů
Ukázkovou aplikaci můžete použít k externímu vložení řídicího panelu. Aplikace obsahují pokyny a kód, které inicializuje potřebnou výměnu tokenů ke generování tokenů s vymezeným oborem. Následující bloky kódu nemají žádné závislosti. Zkopírujte a uložte jednu z následujících aplikací.
Python
Zkopírujte a uložte ho do souboru s názvem example.py.
#!/usr/bin/env python3
import os
import sys
import json
import base64
import urllib.request
import urllib.parse
from http.server import HTTPServer, BaseHTTPRequestHandler
# -----------------------------------------------------------------------------
# Config
# -----------------------------------------------------------------------------
CONFIG = {
"instance_url": os.environ.get("INSTANCE_URL"),
"dashboard_id": os.environ.get("DASHBOARD_ID"),
"service_principal_id": os.environ.get("SERVICE_PRINCIPAL_ID"),
"service_principal_secret": os.environ.get("SERVICE_PRINCIPAL_SECRET"),
"external_viewer_id": os.environ.get("EXTERNAL_VIEWER_ID"),
"external_value": os.environ.get("EXTERNAL_VALUE"),
"workspace_id": os.environ.get("WORKSPACE_ID"),
"port": int(os.environ.get("PORT", 3000)),
}
basic_auth = base64.b64encode(
f"{CONFIG['service_principal_id']}:{CONFIG['service_principal_secret']}".encode()
).decode()
# -----------------------------------------------------------------------------
# HTTP Request Helper
# -----------------------------------------------------------------------------
def http_request(url, method="GET", headers=None, body=None):
headers = headers or {}
if body is not None and not isinstance(body, (bytes, str)):
raise ValueError("Body must be bytes or str")
req = urllib.request.Request(url, method=method, headers=headers)
if body is not None:
if isinstance(body, str):
body = body.encode()
req.data = body
try:
with urllib.request.urlopen(req) as resp:
data = resp.read().decode()
try:
return {"data": json.loads(data)}
except json.JSONDecodeError:
return {"data": data}
except urllib.error.HTTPError as e:
raise RuntimeError(f"HTTP {e.code}: {e.read().decode()}") from None
# -----------------------------------------------------------------------------
# Token logic
# -----------------------------------------------------------------------------
def get_scoped_token():
# 1. Get all-api token
oidc_res = http_request(
f"{CONFIG['instance_url']}/oidc/v1/token",
method="POST",
headers={
"Content-Type": "application/x-www-form-urlencoded",
"Authorization": f"Basic {basic_auth}",
},
body=urllib.parse.urlencode({
"grant_type": "client_credentials",
"scope": "all-apis"
})
)
oidc_token = oidc_res["data"]["access_token"]
# 2. Get token info
token_info_url = (
f"{CONFIG['instance_url']}/api/2.0/lakeview/dashboards/"
f"{CONFIG['dashboard_id']}/published/tokeninfo"
f"?external_viewer_id={urllib.parse.quote(CONFIG['external_viewer_id'])}"
f"&external_value={urllib.parse.quote(CONFIG['external_value'])}"
)
token_info = http_request(
token_info_url,
headers={"Authorization": f"Bearer {oidc_token}"}
)["data"]
# 3. Generate scoped token
params = token_info.copy()
authorization_details = params.pop("authorization_details", None)
params.update({
"grant_type": "client_credentials",
"authorization_details": json.dumps(authorization_details)
})
scoped_res = http_request(
f"{CONFIG['instance_url']}/oidc/v1/token",
method="POST",
headers={
"Content-Type": "application/x-www-form-urlencoded",
"Authorization": f"Basic {basic_auth}",
},
body=urllib.parse.urlencode(params)
)
return scoped_res["data"]["access_token"]
# -----------------------------------------------------------------------------
# HTML generator
# -----------------------------------------------------------------------------
def generate_html(token):
return f"""<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dashboard Demo</title>
<style>
body {{ font-family: system-ui; margin: 0; padding: 20px; background: #f5f5f5; }}
.container {{ max-width: 1200px; margin: 0 auto; height:calc(100vh - 40px) }}
</style>
</head>
<body>
<div id="dashboard-content" class="container"></div>
<script type="module">
import {{ DatabricksDashboard }} from "https://cdn.jsdelivr.net/npm/@databricks/aibi-client@0.0.0-alpha.7/+esm";
const dashboard = new DatabricksDashboard({{
instanceUrl: "{CONFIG['instance_url']}",
workspaceId: "{CONFIG['workspace_id']}",
dashboardId: "{CONFIG['dashboard_id']}",
token: "{token}",
container: document.getElementById("dashboard-content")
}});
dashboard.initialize();
</script>
</body>
</html>"""
# -----------------------------------------------------------------------------
# HTTP server
# -----------------------------------------------------------------------------
class RequestHandler(BaseHTTPRequestHandler):
def do_GET(self):
if self.path != "/":
self.send_response(404)
self.send_header("Content-Type", "text/plain")
self.end_headers()
self.wfile.write(b"Not Found")
return
try:
token = get_scoped_token()
html = generate_html(token)
status = 200
except Exception as e:
html = f"<h1>Error</h1><p>{e}</p>"
status = 500
self.send_response(status)
self.send_header("Content-Type", "text/html")
self.end_headers()
self.wfile.write(html.encode())
def start_server():
missing = [k for k, v in CONFIG.items() if not v]
if missing:
print(f"Missing: {', '.join(missing)}", file=sys.stderr)
sys.exit(1)
server = HTTPServer(("localhost", CONFIG["port"]), RequestHandler)
print(f":rocket: Server running on http://localhost:{CONFIG['port']}")
try:
server.serve_forever()
except KeyboardInterrupt:
sys.exit(0)
if __name__ == "__main__":
start_server()
JavaScript
Zkopírujte a uložte ho do souboru s názvem example.js.
#!/usr/bin/env node
const http = require('http');
const https = require('https');
const { URL, URLSearchParams } = require('url');
// This constant is just a mapping of environment variables to their respective
// values.
const CONFIG = {
instanceUrl: process.env.INSTANCE_URL,
dashboardId: process.env.DASHBOARD_ID,
servicePrincipalId: process.env.SERVICE_PRINCIPAL_ID,
servicePrincipalSecret: process.env.SERVICE_PRINCIPAL_SECRET,
externalViewerId: process.env.EXTERNAL_VIEWER_ID,
externalValue: process.env.EXTERNAL_VALUE,
workspaceId: process.env.WORKSPACE_ID,
port: process.env.PORT || 3000,
};
const basicAuth = Buffer.from(`${CONFIG.servicePrincipalId}:${CONFIG.servicePrincipalSecret}`).toString('base64');
// ------------------------------------------------------------------------------------------------
// Main
// ------------------------------------------------------------------------------------------------
function startServer() {
const missing = Object.keys(CONFIG).filter((key) => !CONFIG[key]);
if (missing.length > 0) throw new Error(`Missing: ${missing.join(', ')}`);
const server = http.createServer(async (req, res) => {
// This is a demo server, we only support GET requests to the root URL.
if (req.method !== 'GET' || req.url !== '/') {
res.writeHead(404, { 'Content-Type': 'text/plain' });
res.end('Not Found');
return;
}
let html = '';
let status = 200;
try {
const token = await getScopedToken();
html = generateHTML(token);
} catch (error) {
html = `<h1>Error</h1><p>${error.message}</p>`;
status = 500;
} finally {
res.writeHead(status, { 'Content-Type': 'text/html' });
res.end(html);
}
});
server.listen(CONFIG.port, () => {
console.log(`🚀 Server running on http://localhost:${CONFIG.port}`);
});
process.on('SIGINT', () => process.exit(0));
process.on('SIGTERM', () => process.exit(0));
}
async function getScopedToken() {
// 1. Get all-api token. This will allow you to access the /tokeninfo
// endpoint, which contains the information required to generate a scoped token
const {
data: { access_token: oidcToken },
} = await httpRequest(`${CONFIG.instanceUrl}/oidc/v1/token`, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
Authorization: `Basic ${basicAuth}`,
},
body: new URLSearchParams({
grant_type: 'client_credentials',
scope: 'all-apis',
}),
});
// 2. Get token info. This information is **required** for generating a token that is correctly downscoped.
// A correctly downscoped token will only have access to a handful of APIs, and within those APIs, only
// a the specific resources required to render the dashboard.
//
// This is essential to prevent leaking a privileged token.
//
// At the time of writing, OAuth tokens in Databricks are valid for 1 hour.
const tokenInfoUrl = new URL(
`${CONFIG.instanceUrl}/api/2.0/lakeview/dashboards/${CONFIG.dashboardId}/published/tokeninfo`,
);
tokenInfoUrl.searchParams.set('external_viewer_id', CONFIG.externalViewerId);
tokenInfoUrl.searchParams.set('external_value', CONFIG.externalValue);
const { data: tokenInfo } = await httpRequest(tokenInfoUrl.toString(), {
headers: { Authorization: `Bearer ${oidcToken}` },
});
// 3. Generate scoped token. This call is very similar to what was issued before, but now we are providing the scoping to make the generated token
// safe to pass to a browser.
const { authorization_details, ...params } = tokenInfo;
const {
data: { access_token },
} = await httpRequest(`${CONFIG.instanceUrl}/oidc/v1/token`, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
Authorization: `Basic ${basicAuth}`,
},
body: new URLSearchParams({
grant_type: 'client_credentials',
...params,
authorization_details: JSON.stringify(authorization_details),
}),
});
return access_token;
}
startServer();
// ------------------------------------------------------------------------------------------------
// Helper functions
// ------------------------------------------------------------------------------------------------
/**
* Helper function to create HTTP requests.
* @param {string} url - The URL to make the request to.
* @param {Object} options - The options for the request.
* @param {string} options.method - The HTTP method to use.
* @param {Object} options.headers - The headers to include in the request.
* @param {Object} options.body - The body to include in the request.
* @returns {Promise<Object>} A promise that resolves to the response data.
*/
function httpRequest(url, { method = 'GET', headers = {}, body } = {}) {
return new Promise((resolve, reject) => {
const isHttps = url.startsWith('https://');
const lib = isHttps ? https : http;
const options = new URL(url);
options.method = method;
options.headers = headers;
const req = lib.request(options, (res) => {
let data = '';
res.on('data', (chunk) => (data += chunk));
res.on('end', () => {
if (res.statusCode >= 200 && res.statusCode < 300) {
try {
resolve({ data: JSON.parse(data) });
} catch {
resolve({ data });
}
} else {
reject(new Error(`HTTP ${res.statusCode}: ${data}`));
}
});
});
req.on('error', reject);
if (body) {
if (typeof body === 'string' || Buffer.isBuffer(body)) {
req.write(body);
} else if (body instanceof URLSearchParams) {
req.write(body.toString());
} else {
req.write(JSON.stringify(body));
}
}
req.end();
});
}
function generateHTML(token) {
return `<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dashboard Demo</title>
<style>
body { font-family: system-ui; margin: 0; padding: 20px; background: #f5f5f5; }
.container { max-width: 1200px; margin: 0 auto; height:calc(100vh - 40px) }
</style>
</head>
<body>
<div id="dashboard-content" class="container"></div>
<script type="module">
/**
* We recommend bundling the dependency instead of using a CDN. However, for demonstration purposes,
* we are just using a CDN.
*
* We do not recommend one CDN over another and encourage decoupling the dependency from third-party code.
*/
import { DatabricksDashboard } from "https://cdn.jsdelivr.net/npm/@databricks/aibi-client@0.0.0-alpha.7/+esm";
const dashboard = new DatabricksDashboard({
instanceUrl: "${CONFIG.instanceUrl}",
workspaceId: "${CONFIG.workspaceId}",
dashboardId: "${CONFIG.dashboardId}",
token: "${token}",
container: document.getElementById("dashboard-content")
});
dashboard.initialize();
</script>
</body>
</html>`;
}
Krok 5: Spuštění ukázkové aplikace
Nahraďte následující hodnoty a spusťte blok kódu z terminálu. Hodnoty by neměly být ohraničené úhlovými závorkami (< >):
- Pomocí adresy URL pracovního prostoru vyhledejte a nahraďte následující hodnoty:
<your-instance><workspace_id><dashboard_id>
- Nahraďte následující hodnoty hodnotami, které jste vytvořili při vytváření instančního objektu (krok 2):
<service_principal_id>-
<service_principal_secret>(tajný klíč klienta)
- Nahraďte následující hodnoty identifikátory přidruženými uživatelům externí aplikace:
<some-external-viewer><some-external-value>
- Nahraďte
</path/to/example>cestou k.pysouboru,.jskterý jste vytvořili v předchozím kroku. Zahrňte příponu souboru.
Poznámka:
Do hodnoty nezahrnujte žádné identifikovatelné osobní údaje (PII EXTERNAL_VIEWER_ID ).
INSTANCE_URL='https://<your-instance>.databricks.com' \
WORKSPACE_ID='<workspace_id>' \
DASHBOARD_ID='<dashboard_id>' \
SERVICE_PRINCIPAL_ID='<service-principal-id>' \
SERVICE_PRINCIPAL_SECRET='<service-principal_secret>' \
EXTERNAL_VIEWER_ID='<some-external-viewer>' \
EXTERNAL_VALUE='<some-external-value>' \
~</path/to/example>
# Terminal will output: :rocket: Server running on http://localhost:3000