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.
Importante
Esse recurso está em Visualização Pública.
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 uma entidade de serviço e tokens de acesso com escopo para autenticar e autorizar o acesso a dashboards inseridos. 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
/tokeninfoponto de extremidade usando esse token, passando informações básicas do usuário, comoexternal_viewer_ideexternal_value. Consulte dashboards com segurança para usuários individuais. -
Geração de token com escopo do usuário: Usando a resposta do ponto de
/tokeninfoextremidade e do ponto de extremidade do Databricks OpenID Connect (OIDC), o servidor gera um novo token com escopo rígido que codifica as informações do usuário passadas. -
Renderização do painel e filtragem de dados: A página do
DatabricksDashboardaplicativo cria uma instância@databricks/aibi-cliente passa o token no escopo do 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 fazer referência__aibi_external_valueà aplicação de filtros por usuário, garantindo que cada visualizador veja apenas os dados que têm permissão para exibir.
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. A external_viewer_id opção é emparelhada com uma external_valuevariá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 de identificação pessoal. 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 instalaçã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. A entidade de serviço deve receber acesso de leitura para o painel e 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 ponto de extremidade da /tokeninfo API, um ponto de extremidade do OpenID Connect (OIDC) que retorna informações básicas do perfil do usuário, incluindo os valores e external_value os external_viewer_id valores. Esses valores permitem associar solicitações a usuários individuais.
Usando o token obtido da entidade de serviço, o servidor gera um novo token com escopo para o usuário específico que está acessando o painel. Esse token com escopo de usuário é passado para a página do aplicativo, em que o aplicativo cria uma instância do DatabricksDashboard objeto da @databricks/aibi-client biblioteca. 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ê deve ter pelo menos 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 inserido. Você pode usar seu próprio aplicativo ou usar os aplicativos de exemplo fornecidos.
Etapa 1: Criar uma entidade de serviço
Crie uma entidade 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 uma entidade de serviço:
- 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 acesse no painel esquerdo.
- Ao lado de Entidades de serviço, clique em Gerenciar.
- Clique em Adicionar entidade de serviço.
- Clique em Adicionar nova.
- Insira um nome descritivo para a entidade de serviço.
- Clique em Adicionar.
- Abra a entidade de serviço que você acabou de criar na página de listagem de entidades de serviço . Use o campo Filtrar entrada de texto para pesquisá-lo pelo nome, se necessário.
- Na página de detalhes da entidade de serviço, registre a ID do aplicativo. Verifique se as caixas de seleção de acesso ao sql do Databricks e do workspace estão selecionadas.
Etapa 2: Criar um segredo OAuth
Gere um segredo para a entidade de serviço e colete os seguintes valores de configuração, que você precisará para 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 token de acesso de 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 na barra lateral do workspace para abrir a página de listagem do painel.
- 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 à entidade de serviço.
Para ler e exibir dados, a entidade de serviço deve ter pelo menos SELECT privilégios nas tabelas e exibiçõ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 iniciam a troca de tokens necessária para gerar tokens com escopo. 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.pyarquivo criado.jsna 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