Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
Esta página descreve como a inserção para usuários externos funciona, como configurar seu workspace do Azure Databricks para compartilhamento seguro de dashboards inseridos e como usar aplicativos de exemplo para começar. A inserção para usuários externos usa um principal de serviço e tokens de acesso com escopo para autenticar e autorizar o acesso a painéis incorporados. Essa abordagem permite compartilhar painéis com visualizadores fora da sua organização, como parceiros e clientes, sem provisionar contas do Azure Databricks para esses usuários.
Para saber mais sobre outras opções de inserção, incluindo a inserção de painéis para usuários em sua organização, consulte Inserir um painel.
Como funciona a inserção para usuários externos
O diagrama e as etapas numeradas a seguir explicam como os usuários são autenticados e os painéis são preenchidos com resultados no escopo do usuário quando você inseri um painel para usuários externos.
- Autenticação e solicitação do usuário: O usuário entra no aplicativo. O front-end do aplicativo envia uma solicitação autenticada ao servidor para um token de acesso do painel.
-
Autenticação da entidade de serviço: Seu servidor usa o segredo da entidade de serviço para solicitar e receber um token OAuth do servidor Databricks. Esse é um token de escopo amplo que pode chamar todas as APIs de painel às quais o Azure Databricks tem acesso em nome da entidade de serviço. Seu servidor chama o
/tokeninfoendpoint usando esse token, passando informações básicas do usuário, comoexternal_viewer_ideexternal_value. Consulte apresente dashboards com segurança para usuários individuais. -
Geração de token com escopo do usuário: Usando a resposta do
/tokeninfoendpoint e do endpoint do Databricks OpenID Connect (OIDC), o servidor gera um novo token rigidamente definido que codifica as informações do usuário passadas. -
Renderização do painel e filtragem de dados: A página do
DatabricksDashboardaplicativo instancia@databricks/aibi-cliente passa o token escopado pelo usuário durante a construção. O painel é renderizado com o contexto do usuário. Esse token autoriza o acesso, dá suporte à auditoria comexternal_viewer_ide carregaexternal_valuepara filtragem de dados. Consultas em conjuntos de dados de painel podem referenciar__aibi_external_valuepara aplicar filtros por usuário, garantindo que cada visualizador veja apenas os dados que são permitidos ver.
Ask Genie não está disponível na inserção externa
Não há suporte para o botão Perguntar Genie na inserção para usuários externos. Se você quiser fornecer recursos de consulta de dados de linguagem natural para usuários externos, use a API de Conversa do Genie . A API de Conversa permite que você integre a funcionalidade do Genie ao aplicativo programaticamente, independentemente da inserção do painel.
Para os dashboards incorporados com incorporação básica, o Ask Genie está disponível. Consulte Ask Genie em dashboards incorporados.
Apresentar dashboards com segurança para usuários individuais
Configure seu servidor de aplicativos para gerar um token exclusivo com escopo de usuário para cada usuário com base em seu external_viewer_id. Isso permite que você acompanhe as exibições do painel e o uso por meio de logs de auditoria.
external_viewer_id é emparelhado com um external_value, que atua como uma variável global que pode ser inserida em consultas SQL usadas em conjuntos de dados do painel. Isso permite filtrar os dados exibidos no painel para cada usuário.
external_viewer_id é passado para os logs de auditoria do painel e não deve incluir informações pessoalmente identificáveis. Esse valor também deve ser exclusivo por usuário.
external_value é usado no processamento de consulta e pode incluir informações de identificação pessoal.
O exemplo a seguir demonstra como usar o valor externo como um filtro em consultas de conjunto de dados:
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 das etapas que você precisa executar para configurar para inserir um dashboard em um local externo.
Para inserir um dashboard em um aplicativo externo, primeiro crie uma entidade de serviço no Azure Databricks e gere um segredo. O principal de serviço deve receber acesso de leitura ao painel e aos seus dados subjacentes. Seu servidor usa o segredo da entidade de serviço para recuperar um token que pode acessar APIs do painel em nome da entidade de serviço. Com esse token, o servidor chama o endpoint da API /tokeninfo, um endpoint OpenID Connect (OIDC) 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 o token obtido do principal de serviço, o servidor gera um novo token direcionado ao usuário específico que está acessando o painel. Esse token com escopo de usuário é passado para a página do aplicativo, onde o aplicativo instancia o objeto da biblioteca DatabricksDashboard@databricks/aibi-client. O token carrega informações específicas do usuário que dão suporte à auditoria e impõe a filtragem para que cada usuário veja apenas os dados que está autorizado a acessar. Do ponto de vista do usuário, fazer logon no aplicativo automaticamente fornece acesso ao painel inserido com a visibilidade correta dos dados.
Limites de taxa e considerações de desempenho
A inserção externa tem um limite de taxa de 20 cargas de painel por segundo. Você pode abrir mais de 20 dashboards ao mesmo tempo, mas não mais do que 20 podem começar a carregar simultaneamente.
Pré-requisitos
Para implementar a inserção externa, verifique se você atende aos seguintes pré-requisitos:
- Você precisa ter, no mínimo, permissões CAN MANAGE em um painel publicado. Confira o Tutorial: Use painéis de exemplo para criar e publicar rapidamente um painel de exemplo, se necessário.
- Você deve ter a CLI do Databricks versão 0.205 ou superior instalada. Consulte Instalar ou atualizar a CLI do Databricks para obter instruções. Para configurar e usar a autenticação OAuth, consulte a autenticação U2M (usuário para computador) do OAuth.
- Um administrador de workspace deve definir uma lista de domínios aprovados que podem hospedar o painel inserido. Consulte Gerenciar a inserçã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 exemplo fornecidos.
Etapa 1: Criar um principal do serviço
Crie um principal de serviço para atuar como a identidade do aplicativo externo no Azure Databricks. Essa entidade de serviço autentica solicitações em nome do seu aplicativo.
Para criar um service principal:
- Como administrador do workspace, faça login no workspace do Azure Databricks.
- Clique no nome de usuário na barra superior do workspace do Azure Databricks e selecione Configurações.
- Clique em Identidade e acesso no painel lateral esquerdo.
- Ao lado de Entidades de serviço, clique em Gerenciar.
- Clique em Adicionar entidade de serviço.
- Clique em Adicionar novo.
- Insira um nome descritivo para a entidade de serviço.
- Clique em Adicionar.
- Abra o principal de serviço que você acabou de criar na página de listagem de princípios de serviço. Use o campo Filtrar entrada de texto para pesquisá-lo pelo nome, se necessário.
- Na página principal de serviço, registre a ID do aplicativo. Verifique se as caixas de seleção de acesso ao Databricks SQL e acesso ao Workspace estão selecionadas.
Etapa 2: Criar um segredo OAuth
Gere um segredo para o principal de serviço e colete os seguintes valores de configuração, que você precisará para seu aplicativo externo.
- ID do principal de serviço (cliente)
- Segredo do cliente
O principal de serviço usa um segredo OAuth para verificar sua identidade ao solicitar um token de acesso do seu aplicativo externo.
Para gerar um segredo:
- Clique em Segredos na página de detalhes da entidade de serviço .
- Clique em Gerar segredo.
- Insira um valor de vida para o novo segredo em dias (por exemplo, entre 1 e 730 dias).
- Copie o segredo imediatamente. Não é possível exibir esse segredo novamente depois de sair dessa tela.
Etapa 3: Atribuir permissões à entidade de serviço
A entidade de serviço que você criou atua como a identidade que fornece acesso ao painel por meio do aplicativo. Suas permissões serão aplicadas somente se o painel não for publicado com permissões de dados compartilhadas. Se as permissões de dados compartilhados forem usadas, as credenciais do editor acessarão dados. Para obter mais detalhes e recomendações, consulte abordagens de autenticação do Embedding.
- Clique em Painéis de controle na barra lateral do workspace para abrir a página de listagem de painéis.
- Clique no nome do painel que você deseja inserir. O painel publicado é aberto.
- Clique em Compartilhar.
- Use o campo de entrada de texto na caixa de diálogo Compartilhamento para localizar sua entidade de serviço e clique nele. Defina o nível de permissão como CAN RUN. Em seguida, clique em Adicionar.
- Registre a ID do painel. Você pode encontrar a ID do painel na URL do painel (por exemplo,
https://<your-workspace-url>/dashboards/<dashboard-id>). Confira os detalhes do workspace do Databricks.
Observação
Se você publicar um painel com permissões de dados individuais, deverá conceder ao principal de serviço acesso aos dados usados no painel. O acesso de computação sempre usa as credenciais do editor, portanto, você não precisa conceder permissões de computação ao principal de serviço.
Para ler e exibir dados, o principal de serviço deve ter pelo menos SELECT privilégios nas tabelas e visões referenciadas no painel. Veja quem pode gerenciar privilégios?.
Etapa 4: Usar o aplicativo de exemplo para autenticar e gerar tokens
Use um aplicativo de exemplo para praticar a inserção externa do painel. Os aplicativos incluem instruções e código que inicia a troca de token necessária para gerar tokens delimitados. Os blocos de código a seguir não têm dependências. Copie e salve um dos aplicativos a seguir.
Python
Copie e salve-o 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()
JavaScript
Copie e salve-o 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>`;
}
Etapa 5: Executar o aplicativo de exemplo
Substitua os valores a seguir e execute o bloco de código do terminal. Seus valores não devem ser cercados por colchetes angulares (< >):
- Use a URL do workspace para localizar e substituir os seguintes valores:
<your-instance><workspace_id><dashboard_id>
- Substitua os valores a seguir pelos valores que você criou ao criar a entidade de serviço (etapa 2):
<service_principal_id>-
<service_principal_secret>(segredo do cliente)
- Substitua os seguintes valores por identificadores associados aos usuários do aplicativo externo:
<some-external-viewer><some-external-value>
- Substitua
</path/to/example>pelo caminho para o arquivo.pyou.jsque você criou na etapa anterior. Inclua a extensão de arquivo.
Observação
Não inclua nenhuma PII (informação de identificação pessoal) no EXTERNAL_VIEWER_ID valor.
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
Opções de configuração
Ao inicializar DatabricksDashboard, você pode passar um config objeto para personalizar a aparência e o comportamento do painel inserido. O config objeto requer um version campo definido como 1.
Ocultar o logotipo do Databricks
Por padrão, os painéis incorporados exibem um logotipo Powered by Databricks no rodapé. Para ocultar o logotipo, defina hideDatabricksLogo: true no objeto 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();