O que é incorporação para usuários externos?
Visualização
Esse recurso está em Public Preview.
Esta página descreve como funciona a incorporação para usuários externos, como configurar seu Databricks workspace para compartilhamento seguro de painéis incorporados e como usar aplicativos de amostra para começar. A incorporação para usuários externos usa uma entidade de serviço e o escopo access tokens para autenticar e autorizar o acesso a painéis incorporados. Essa abordagem permite que o senhor compartilhe painéis com visualizadores fora da sua organização, como parceiros e clientes, sem provisionar uma conta Databricks para esses usuários.
Para saber mais sobre outras opções de incorporação, incluindo a incorporação de painéis para usuários em sua organização, consulte Incorporar um painel.
Como funciona a incorporação para usuários externos
O diagrama e os passos numerados a seguir explicam como os usuários são autenticados e como os painéis são preenchidos com resultados de escopo do usuário quando você incorpora um painel para usuários externos.
- Autenticação e solicitação do usuário: o usuário faz login no seu aplicativo. O frontend do seu aplicativo envia uma solicitação autenticada ao seu servidor para um painel access token.
- autenticação da entidade de serviço: Seu servidor usa o segredo da entidade de serviço para solicitar e receber tokens OAuth do servidor Databricks. Trata-se de um token de escopo amplo que pode chamar todos os painéis APIs aos quais Databricks tem acesso em nome da entidade de serviço. Seu servidor chama o
/tokeninfo
endpoint usando esses tokens, passando informações básicas do usuário, comoexternal_viewer_id
eexternal_value
. Consulte Apresentar painéis com segurança para usuários individuais. - Geração de tokens com escopo de usuário): Usando a resposta do
/tokeninfo
endpoint e do Databricks OpenID Connect (OIDC) endpoint, seu servidor gera um novo token de escopo restrito que codifica as informações do usuário que o senhor forneceu. - Renderização do painel e filtragem de dados: A página do aplicativo instancia
DatabricksDashboard
a partir de@databricks/aibi-client
e passa os tokens com escopo de usuário durante a construção. O painel é renderizado com o contexto do usuário. Esses tokens autorizam o acesso, oferecem suporte à auditoria comexternal_viewer_id
e contêmexternal_value
para filtragem de dados. As consultas no conjunto de dados do painel podem fazer referência a__aibi_external_value
para aplicar filtros por usuário, garantindo que cada visualizador veja apenas os dados que tem permissão para ver view.
Apresente painéis com segurança para usuários individuais
Configure o servidor de aplicativos para gerar tokens exclusivos com escopo de usuário para cada usuário com base no endereço external_viewer_id
. Isso permite que o senhor rastreie a visualização e o uso do painel por meio de auditoria logs. O external_viewer_id
está associado a um external_value
, que atua como uma variável global que pode ser inserida nas consultas SQL usadas no conjunto de dados do painel. Isso permite que você filtre os dados exibidos no painel para cada usuário.
external_viewer_id
é transmitido para a auditoria do seu painel logs e não deve incluir informações de identificação pessoal. Esse valor também deve ser exclusivo por usuário.
external_value
é usado no processamento de consultas e pode incluir informações de identificação pessoal.
O exemplo a seguir demonstra como usar o valor externo como um filtro nas consultas do site dataset:
SELECT *
FROM sales
WHERE region = __aibi_external_value
Visão geral da configuração
Esta seção inclui uma visão geral conceitual de alto nível dos passos que o senhor precisa executar para configurar a incorporação de um painel em um local externo.
Para incorporar um painel em um aplicativo externo, primeiro o senhor cria uma entidade de serviço na Databricks e gera um segredo. A entidade de serviço deve ter acesso de leitura ao painel e aos dados subjacentes. Seu servidor usa o segredo da entidade de serviço para recuperar tokens que podem acessar o dashboard APIs em nome da entidade de serviço. Com esses tokens, o servidor chama o /tokeninfo
API endpoint, um OpenID Connect (OIDC) endpoint que retorna informações básicas do perfil do usuário, incluindo os valores external_value
e external_viewer_id
. Esses valores permitem associar solicitações a usuários individuais.
Usando os tokens obtidos da entidade de serviço, seu servidor gera um novo token com escopo para o usuário específico que está acessando o painel. Esses tokens com escopo de usuário são passados para a página do aplicativo, onde o aplicativo instancia o objeto DatabricksDashboard
da biblioteca @databricks/aibi-client
. Os tokens contêm informações específicas do usuário que dão suporte à auditoria e impõem a filtragem para que cada usuário veja apenas os dados que está autorizado a acessar. Do ponto de vista do usuário, o login no aplicativo fornece automaticamente acesso ao painel incorporado com a visibilidade correta dos dados.
Limites de taxa e considerações de desempenho
A incorporação externa tem um limite de taxa de 20 cargas do painel por segundo. O senhor pode abrir mais de 20 painéis de uma vez, mas não mais do que 20 podem começar a ser carregados simultaneamente.
Pré-requisitos
Para implementar a incorporação externa, certifique-se de atender aos seguintes pré-requisitos:
- O senhor deve ter pelo menos permissões CAN MANAGE em um painel publicado. Veja o tutorial: Use painéis de amostra para criar e publicar rapidamente um painel de exemplo, se necessário.
- O senhor deve ter o site Databricks CLI versão 0.205 ou acima instalado. Consulte Instalar ou atualizar a CLI da Databricks para obter instruções. Para configurar e usar a autenticação OAuth, consulte Autenticação OAuth de usuário para máquina (U2M).
- O administrador do workspace deve definir uma lista de domínios aprovados que podem hospedar o painel incorporado. Consulte gerenciar a incorporação do painel para obter instruções.
- Um aplicativo externo para hospedar seu painel incorporado. Você pode usar seu próprio aplicativo ou usar os aplicativos de amostra fornecidos.
o passo 1: Criar uma entidade de serviço
Crie uma entidade de serviço para atuar como a identidade do seu aplicativo externo na Databricks. Essa entidade de serviço autentica solicitações em nome do seu aplicativo.
Para criar uma entidade de serviço:
- Como administrador do workspace, faça login no workspace do Databricks.
- Clique no seu nome de usuário na barra superior do workspace do Databricks e selecione Configurações .
- Clique em Identidade e acesso no painel esquerdo.
- Ao lado de Entidades de serviço , clique em Gerenciar .
- Clique em Adicionar entidade de serviço .
- Clique em Adicionar novo .
- Digite um nome descritivo para a entidade de serviço.
- Clique em Adicionar .
- Abra a entidade de serviço que o senhor acabou de criar na página de listagem de entidades de serviço . Use o campo Filtrar entrada de texto para procurá-lo por nome, se necessário.
- Na página de detalhes da entidade de serviço , registre o ID do aplicativo . Verifique se as caixas de seleção Databricks SQL access e workspace access estão marcadas.
o passo 2: Criar um segredo OAuth
Gere um segredo para a entidade de serviço e colete os seguintes valores de configuração, que serão necessários para o seu aplicativo externo:
- ID da entidade de serviço (cliente)
- Segredo do cliente
A entidade de serviço usa um segredo OAuth para verificar sua identidade ao solicitar um access token de seu aplicativo externo.
Para gerar um segredo:
- Clique em Secrets (Segredos ) na página de detalhes da entidade de serviço .
- Clique em Gerar segredo .
- Digite o valor da duração da vida para o novo segredo em dias (por exemplo, entre 1 e 730 dias).
- Copie o segredo imediatamente. O senhor não pode view esse segredo novamente depois de sair dessa tela.
o passo 3: Atribuir permissões à sua entidade de serviço
A entidade de serviço que o senhor criou atua como a identidade que fornece acesso ao painel por meio do seu aplicativo. Suas permissões se aplicam somente se o painel não for publicado com credenciais incorporadas. Se credenciais incorporadas forem usadas, as credenciais do editor acessarão os dados. Para obter mais detalhes e recomendações, consulte Como incorporar abordagens de autenticação.
- Clique em Dashboards na barra lateral workspace para abrir a página de listagem de dashboards.
- Clique no nome do painel que você deseja incorporar. O painel publicado é aberto.
- Clique em Compartilhar .
- Use o campo de entrada de texto na caixa de diálogo de compartilhamento para localizar sua entidade de serviço e, em seguida, clique nela. Defina o nível de permissão como CAN RUN . Em seguida, clique em Adicionar .
- Registre o ID do painel . Você pode encontrar o ID do painel na URL do painel (por exemplo,
https://<your-workspace-url>/dashboards/<dashboard-id>
). Consulte Databricks workspace detalhes.
Se o senhor publicar um painel sem credenciais incorporadas, deverá conceder à sua entidade de serviço acesso aos dados usados no painel. O acesso computacional sempre usa as credenciais do editor, portanto, o senhor não precisa conceder permissões compute à entidade de serviço.
Para ler e exibir dados, a entidade de serviço deve ter pelo menos SELECT
privilégios nas tabelas e visualizações referenciadas no painel. Consulte Quem pode gerenciar privilégios?
o passo 4: Use o aplicativo de exemplo para autenticar e gerar tokens
Use um aplicativo de exemplo para praticar a incorporação externa do seu painel. Os aplicativos incluem instruções e códigos que iniciam a troca de tokens necessária para gerar o escopo tokens. Os blocos de código a seguir não têm dependências. Copie e salve um dos seguintes aplicativos.
- Python
- JavaScript
Copie e salve isso em um arquivo chamado 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()
Copie e salve isso em um arquivo chamado 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>`;
}
o passo 5: execução do aplicativo de exemplo
Substitua os valores a seguir e, em seguida, execute o bloco de código em seu terminal. Seus valores não devem estar entre colchetes angulares (< >
):
-
Use o URLworkspace para localizar e substituir os seguintes valores:
<your-instance>
<workspace_id>
<dashboard_id>
-
Substitua os valores a seguir pelos valores que o senhor criou ao criar a entidade de serviço (o passo 2):
<service_principal_id>
<service_principal_secret>
(segredo do cliente)
-
Substitua os valores a seguir por identificadores associados aos usuários do aplicativo externo:
<some-external-viewer>
<some-external-value>
-
Substitua
</path/to/example>
pelo caminho para o arquivo.py
ou.js
que o senhor criou no passo anterior. Inclua a extensão do arquivo.
Não inclua nenhuma informação de identificação pessoal (PII) no valor 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