Примечание.
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
На этой странице описывается, как работает внедрение внешних пользователей, настройка рабочей области Azure Databricks для безопасного доступа к внедренным панелям мониторинга и использование примеров приложений для начала работы. Внедрение для внешних пользователей использует учетную запись службы и токены доступа с ограниченной областью действия для проверки подлинности и авторизации доступа к встроенным панелям мониторинга. Этот подход позволяет делиться панелями мониторинга с пользователями за пределами вашей организации, такими как партнеры и клиенты, без предоставления учетных записей Azure Databricks для этих пользователей.
Дополнительные сведения о других вариантах внедрения, включая внедрение панелей мониторинга для пользователей в организации, см. в статье "Внедрение панели мониторинга".
Как работает встраивание для пользователей вне системы
На схеме и в нумерованных шагах, представленных далее, объясняется, как проходят аутентификацию пользователи и как панели мониторинга наполняются результатами, привязанными к пользователю, когда вы внедряете панель мониторинга для внешних пользователей.
- Проверка подлинности пользователя и запрос: Пользователь входит в приложение. Фронтенд вашего приложения отправляет аутентифицированный запрос на ваш сервер для получения токена доступа к дашборду.
-
Аутентификация служебного принципала: Ваш сервер использует секрет служебного принципала для запроса и получения токена OAuth от сервера Databricks. Это широко используемый токен, который может вызывать все API панели мониторинга, к которым Azure Databricks имеет доступ от имени сервисного субъекта. Сервер вызывает конечную точку
/tokeninfoс помощью этого маркера, передав базовую информацию пользователя, напримерexternal_viewer_idиexternal_value. См. безопасную презентацию панелей мониторинга для отдельных пользователей. -
Создание токенов с областью действия пользователя: Используя ответ от конечной точки
/tokeninfoи конечной точки Databricks OpenID Connect (OIDC), ваш сервер создает новый тщательно ограниченный токен, который кодирует информацию о пользователе, переданную вами. -
Отрисовка панелей мониторинга и фильтрация данных: Страница приложения создает экземпляры
DatabricksDashboardиз@databricks/aibi-clientи передает маркер с областью действия пользователя во время построения. Панель мониторинга отображается в соответствии с контекстом пользователя. Этот токен авторизует доступ, поддерживает аудит с помощьюexternal_viewer_id, и содержитexternal_valueдля фильтрации данных. Запросы в наборах данных панели мониторинга могут ссылаться на__aibi_external_value, чтобы применять фильтры для каждого пользователя, гарантируя, что каждый зритель видит только данные, которые им разрешено просматривать.
Ask Genie недоступен во внешнем внедрении
Кнопка Ask Genie не поддерживается при встраивании для внешних пользователей. Если вы хотите предоставить возможности запросов на естественном языке внешним пользователям, используйте Genie Conversation API. API беседы позволяет интегрировать функциональные возможности Genie в приложение программным способом независимо от внедрения панели мониторинга.
Для панелей мониторинга с базовым внедрением доступна функция Ask Genie. См. статью Ask Genie в внедренных панелях мониторинга.
Безопасное представление панелей мониторинга отдельным пользователям
Настройте сервер приложений для создания уникального пользовательского токена для каждого пользователя на основе их external_viewer_id. Это позволяет отслеживать просмотры панели мониторинга и использование журналов аудита. Он external_viewer_id связан с external_value, который выступает в качестве глобальной переменной, которая может быть вставлена в запросы SQL, используемые в наборах данных панели мониторинга. Это позволяет фильтровать данные, отображаемые на панели мониторинга для каждого пользователя.
external_viewer_id передается в журналы аудита панели мониторинга и не должен включать личные сведения. Это значение также должно быть уникальным для каждого пользователя.
external_value используется в обработке запросов и может включать личные сведения.
В следующем примере показано, как использовать внешнее значение в качестве фильтра в запросах набора данных:
SELECT *
FROM sales
WHERE region = __aibi_external_value
Общие сведения о настройке
В этом разделе представлен концептуальный обзор этапов, которые необходимо выполнить для встраивания панели мониторинга во внешнее размещение.
Чтобы внедрить панель мониторинга во внешнее приложение, сначала создайте служебный принципал /tokeninfo, инициатор OIDC (OpenID Connect), которая возвращает основные сведения профиля пользователя, включая значения external_value и external_viewer_id. Эти значения позволяют связать запросы с отдельными пользователями.
С помощью токена, полученного от служебного субъекта, сервер создает новый токен с ограниченными правами для конкретного пользователя, который обращается к панели мониторинга. Этот пользовательский токен передается на страницу приложения, где приложение создает экземпляр объекта DatabricksDashboard из библиотеки @databricks/aibi-client. Маркер содержит сведения, относящиеся к пользователю, поддерживающие аудит и принудительное фильтрацию, чтобы каждый пользователь видел только данные, к которым они авторизованы для доступа. С точки зрения пользователя вход в приложение автоматически предоставляет доступ к встроенной панели мониторинга с правильной видимостью данных.
Ограничения скорости и рекомендации по производительности
Внешнее встраивание имеет ограничение скорости в 20 загрузок дашборда в секунду. Одновременно можно открыть более 20 панелей мониторинга, но не более 20 могут одновременно загружаться.
Необходимые условия
Чтобы реализовать внешнее внедрение, убедитесь, что выполнены следующие предварительные условия:
- На опубликованной панели мониторинга необходимо иметь как минимум права доступа CAN MANAGE. См. Руководство: использование примеров панелей мониторинга для быстрого создания и публикации примера панели мониторинга, при необходимости.
- Необходимо установить Интерфейс командной строки Databricks версии 0.205 или более поздней. Инструкции см. в статье об установке или обновлении интерфейса командной строки Databricks . Сведения о настройке и использовании проверки подлинности OAuth см. в статье OAuth с проверкой подлинности "пользователь — компьютер" (U2M).
- Администратор рабочей области должен определить список утвержденных доменов, которые могут размещать внедренную панель мониторинга. Инструкции см. в разделе "Управление панелью мониторинга" и внедрением пространства Genie.
- Внешнее приложение для размещения встроенной панели мониторинга. Вы можете использовать собственное приложение или использовать предоставленные примеры приложений.
Шаг 1. Создание служебного принципала
Создайте учетную запись службы для предоставления удостоверения внешнему приложению в среде Azure Databricks. Эта учетная запись службы аутентифицирует запросы от имени вашего приложения.
Чтобы создать субъект-службу, выполните приведенные действия.
- Как администратор рабочей области войдите в рабочую область Azure Databricks.
- Щелкните имя пользователя в верхней строке рабочей области Azure Databricks и выберите Settings.
- Щелкните Удостоверение и доступ на панели слева.
- Рядом с учетными записями служб нажмите Управление.
- Нажмите Добавить учетную запись службы.
- Нажмите кнопку "Добавить новую".
- Введите описательное имя субъекта-службы.
- Нажмите кнопку Добавить.
- Откройте служебный принципал, который вы только что создали, на странице списка служебных принципалов. При необходимости используйте поле ввода текста фильтра для поиска по имени.
- На странице сведений о субъекте-пользователе запишите Идентификатор приложения. Убедитесь, что установлены флажки доступа Databricks SQL и доступа к рабочей области.
Шаг 2. Создание секрета OAuth
Создайте секрет для учетной записи службы и соберите следующие параметры конфигурации, которые потребуются для внешнего приложения.
- Идентификатор сервисного принципала (клиента)
- Секрет клиента
Субъект-служба использует секрет OAuth для проверки его удостоверения при запросе маркера доступа из внешнего приложения.
Создание секрета:
- Щелкните Секреты на странице сведений о служебном принципале.
- Нажмите кнопку "Создать секрет".
- Введите значение времени существования нового секрета в днях (например, от 1 до 730 дней).
- Скопируйте секрет немедленно. Вы не можете снова просмотреть этот секрет после выхода из этого экрана.
Шаг 3. Назначьте разрешения для вашей учетной записи службы
Созданный служебный принципал является идентификацией, предоставляющей доступ к дашборду с помощью вашего приложения. Его разрешения действуют только в том случае, если панель мониторинга не опубликована с общими разрешениями данных. Если используются разрешения на доступ к общим данным, учетные данные издателя предоставляют доступ к ним. Дополнительные сведения и рекомендации см. в разделе " Внедрение подходов к проверке подлинности".
- Щелкните панели мониторинга на боковой панели рабочей области, чтобы открыть страницу списка панелей мониторинга.
- Щелкните имя панели мониторинга, которую вы хотите внедрить. Откроется опубликованная панель мониторинга.
- Нажмите Поделиться.
- Используйте текстовое поле в диалоговом окне Общий доступ, чтобы найти главный сервис и нажать на него. Задайте для уровня разрешений значение CAN RUN. Затем нажмите кнопку "Добавить".
- Запишите идентификатор панели мониторинга. Идентификатор панели мониторинга можно найти в URL-адресе панели мониторинга (например,
https://<your-workspace-url>/dashboards/<dashboard-id>). См. сведения о рабочей области Databricks.
Замечание
При публикации дешборда с индивидуальными разрешениями на доступ к данным необходимо предоставить служебному компоненту доступ к данным, используемым в дешборде. Доступ к вычислениям всегда использует учетные данные издателя, поэтому вам не нужно предоставлять разрешения на вычисления служебному принципалу.
Для чтения и отображения данных служебная учетная запись должна иметь по меньшей мере SELECT привилегии на таблицы и представления, на которые ссылается панель мониторинга. См. раздел "Кто может управлять привилегиями?".
Шаг 4. Использование примера приложения для проверки подлинности и создания маркеров
Используйте пример приложения для практики внешнего внедрения панели мониторинга. Приложения включают инструкции и код, инициирующий необходимый обмен токенами для генерации ограниченных токенов. Следующие блоки кода не имеют зависимостей. Скопируйте и сохраните одно из следующих приложений.
Python
Скопируйте и сохраните его в файле с именем 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
Скопируйте и сохраните его в файле с именем 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>`;
}
Шаг 5. Запуск примера приложения
Замените следующие значения, а затем запустите блок кода из терминала. Ваши значения не должны быть окружены угловыми скобками (< >).
- Используйте URL-адрес рабочей области для поиска и замены следующих значений:
<your-instance><workspace_id><dashboard_id>
- Замените следующие значения значениями, созданными при создании субъекта-службы (шаг 2).
<service_principal_id>-
<service_principal_secret>(секрет клиента)
- Замените следующие значения идентификаторами, связанными с пользователями внешнего приложения:
<some-external-viewer><some-external-value>
- Замените
</path/to/example>на путь к файлу.pyили.js, который вы создали на предыдущем шаге. Включите расширение файла.
Замечание
Не включайте какую-либо личную информацию (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
Параметры конфигурации
При инициализации DatabricksDashboard можно передать объект config, чтобы настроить внешний вид и поведение встроенной панели мониторинга. Объекту config требуется, чтобы поле version имело значение 1.
Скрытие логотипа Databricks
По умолчанию встроенные панели мониторинга отображают логотип Powered by Databricks в нижнем колонтитуле. Чтобы скрыть логотип, установите hideDatabricksLogo: true в объекте config .
const dashboard = new DatabricksDashboard({
instanceUrl: '<your-instance>.databricks.com',
workspaceId: '<workspace_id>',
dashboardId: '<dashboard_id>',
token: '<scoped-token>',
container: document.getElementById('dashboard-content'),
config: {
version: 1,
hideDatabricksLogo: true,
},
});
dashboard.initialize();
Скачивание данных во внешнем внедрении
По умолчанию зрители встроенных панелей мониторинга могут загружать данные в формате CSV, TSV или Excel файлы и скачивать визуализации в виде PNG-файлов. Чтобы отключить скачивание данных в рабочей области, администратор рабочей области может отключить параметр загрузки в консоли администрирования рабочей области.