Looker 使用 OAuth 让 OAuth 客户端应用能够向 Looker API 进行身份验证,而无需向运行 OAuth 客户端应用的浏览器公开客户端 ID 和客户端密钥。
使用 OAuth 的 Web 应用必须满足以下要求:
- 只有 Looker API 4.0 支持使用 OAuth 进行身份验证。
- OAuth 客户端应用必须先使用 API 向 Looker 注册 OAuth 客户端应用,然后该应用的用户才能向 Looker 进行身份验证。
- 客户端应用必须对所有 Looker API 请求使用 HTTPS。如需使用浏览器提供的
SubtleCrypto
API,客户端应用必须托管在 HTTPS 上。
Looker API CORS 支持
Looker API 支持使用跨域资源共享 (CORS) 协议在浏览器中和跨源调用。Looker CORS 支持具有以下要求:
- 只有嵌入式网域许可名单中列出的来源才能使用 CORS 调用 API。
只有通过 OAuth 或调用
/login
API 端点获取的访问令牌可用于通过 CORS 调用 Looker API。您无法使用 CORS 请求调用
/login
API 端点。如需使用 CORS 请求调用 Looker API,客户端应用必须使用使用 OAuth 执行用户登录中所述的 OAuth 登录流程,或者从应用服务器或非 CORS API 调用中检索令牌。
OAuth 身份验证概览
OAuth 身份验证流程的概述如下:
- 向 Looker API 注册 OAuth 客户端应用。
- 将 OAuth 客户端应用的来源添加到嵌入式网域许可名单中,以便进行代码交换 API 调用和任何后续 CORS API 调用。
- 当 OAuth 客户端应用尝试对用户进行身份验证时,将浏览器网址重定向到 Looker 界面主机名(而非 Looker API 主机名)上的
/auth
端点。例如https://instance_name.looker.com
。 - 如果用户成功通过身份验证并登录 Looker,Looker 会立即返回一个指向 OAuth 客户端应用的 OAuth 重定向。如果用户尚未在设备和浏览器中登录 Looker,系统会显示 Looker 登录屏幕,并提示用户使用常规身份验证协议登录 Looker 用户账号。
- 接下来,您的 OAuth 客户端应用应使用 OAuth 重定向中返回的授权代码调用 Looker API 主机名(例如
https://instance_name.looker.com:19999
)上的/token
端点。API 主机名可能与 Looker 界面主机名相同或不同。/token
端点仅存在于 Looker API 主机上,/auth
端点仅存在于 Looker 界面主机上。 - 如果传递给
/token
端点的授权代码有效,Looker 会返回一个 APIaccess_token
,该 API 已针对来自 OAuth 客户端应用网域的 CORS API 请求启用。
注册 OAuth 客户端应用
尝试使用 OAuth 向 Looker API 进行身份验证的每个 OAuth 客户端应用都必须先在 Looker 实例中注册,然后 Looker 才会授予访问权限。要注册 OAuth 客户端应用,请执行以下操作:
- 在 Looker 实例上打开 API Explorer。
- 使用版本下拉菜单,选择 4.0 - 稳定版 API。
在 Auth 方法下,找到
register_oauth_client_app()
API 端点。您也可以在搜索字段中搜索“oauth app”。您可以使用register_oauth_client_app()
向 Looker 注册 OAuth 客户端应用。点击运行按钮,在 API Explorer 中输入参数,然后再次点击运行以注册 OAuth 客户端应用,或以编程方式使用register_oauth_client_app()
API 端点。必需的register_oauth_client_app()
参数如下:client_guid
:应用的全局唯一 IDredirect_uri
:应用将在其中接收包含授权代码的 OAuth 重定向的 URIdisplay_name
:向应用用户显示的应用名称description
:应用的说明,在用户首次通过应用登录时,系统会在披露声明和确认页面上向用户显示该说明
client_guid
和redirect_uri
参数的值必须与 OAuth 客户端应用将提供的值完全一致,否则系统将拒绝身份验证。
使用 OAuth 执行用户登录
将用户定向到界面主机上的
/auth
端点。例如:async function oauth_login() { const code_verifier = secure_random(32) const code_challenge = await sha256_hash(code_verifier) const params = { response_type: 'code', client_id: '123456', redirect_uri: 'https://mywebapp.com:3000/authenticated', scope: 'cors_api', state: '1235813', code_challenge_method: 'S256', code_challenge: code_challenge, } const url = `${base_url}?${new URLSearchParams(params).toString()}` // Replace base_url with your full Looker instance's UI host URL, plus the `/auth` endpoint. log(url) // Stash the code verifier we created in sessionStorage, which // will survive page loads caused by login redirects // The code verifier value is needed after the login redirect // to redeem the auth_code received for an access_token // sessionStorage.setItem('code_verifier', code_verifier) document.location = url } function array_to_hex(array) { return Array.from(array).map(b => b.toString(16).padStart(2,'0')).join('') } function secure_random(byte_count) { const array = new Uint8Array(byte_count); crypto.getRandomValues(array); return array_to_hex(array) } async function sha256_hash(message) { const msgUint8 = new TextEncoder().encode(message) const hashBuffer = await crypto.subtle.digest('SHA-256', msgUint8) return base64.urlEncode(hashBuffer)) // Refers to the implementation of base64.encode stored at https://gist.github.com/jhurliman/1250118 }
Looker 会尝试使用为 Looker 实例配置的身份验证系统对用户进行身份验证。
- 如果用户已在当前浏览器中登录 Looker(即存在有效的登录 Cookie 状态),系统不会提示用户输入登录凭据。
- 如果这是该用户首次使用此 OAuth 客户端应用登录,Looker 会显示一个披露和确认页面,供用户确认并接受。系统会显示注册应用时使用的
description
参数中的文本。说明应指明应用打算如何使用用户的 Looker 账号。当用户点击接受时,页面会重定向到应用redirect_uri
。 - 如果用户已在当前浏览器中登录 Looker 并已确认披露声明页面,则 OAuth 登录会立即完成,不会出现视觉中断。
Looker API 会将 OAuth 重定向返回给 OAuth 客户端应用。保存 URI 参数中列出的授权代码。以下是 OAuth 重定向 URI 示例:
https://mywebapp.com:3000/authenticated?&code=asdfasdfassdf&state=...
授权代码显示在 URI 中的
&code=
后面。在此示例中,授权代码为asdfasdfassdf
。向 Looker API 中的
/token
端点发出 Web 请求,传递授权代码和应用信息。例如:async function redeem_auth_code(response_str) { const params = new URLSearchParams(response_str) const auth_code = params.get('code') if (!auth_code) { log('ERROR: No authorization code in response') return } log(`auth code received: ${auth_code}`) log(`state: ${params.get('state')}`) const code_verifier = sessionStorage.getItem('code_verifier') if (!code_verifier) { log('ERROR: Missing code_verifier in session storage') return } sessionStorage.removeItem('code_verifier') const response = await fetch('https://mycompany.looker.com:19999/api/token', { // This is the URL of your Looker instance's API web service method: 'POST', mode: 'cors', // This line is required so that the browser will attempt a CORS request. body: stringify({ grant_type: 'authorization_code', client_id: '123456', redirect_uri: 'https://mywebapp.com:3000/authenticated', code: auth_code, code_verifier: code_verifier, }), headers: { 'x-looker-appid': 'Web App Auth & CORS API Demo', // This header is optional. 'Content-Type': 'application/json;charset=UTF-8' // This header is required. }, }).catch((error) => { log(`Error: ${error.message}`) }) const info = await response.json() log(`/api/token response: ${stringify(info)}`) // Store the access_token and other info, // which in this example is done in sessionStorage const expires_at = new Date(Date.now() + (info.expires_in * 1000)) info.expires_at = expires_at log(`Access token expires at ${expires_at.toLocaleTimeString()} local time.`) sessionStorage.setItem('access_info', stringify(info)) access_info = info }
成功的响应将为 OAuth 客户端应用提供一个 API
access_token
。该响应还将包含一个refresh_token
,您稍后可以使用它在无用户互动的情况下获取新的access_token
。refresh_token
的生命周期为一个月。安全地存储refresh_token
。Looker 管理员可以随时撤消此系统中的所有令牌。