Looker 使用 OAuth 让 OAuth 客户端应用在 Looker API 中进行身份验证,而不会向运行 OAuth 客户端应用的浏览器公开客户端 ID 和客户端密钥。
使用 OAuth 的 Web 应用必须满足以下要求:
- 使用 OAuth 进行身份验证的功能仅适用于 Looker API 4.0。
- OAuth 客户端应用必须先使用
register_oauth_client_app
API 向 Looker 注册,应用用户才能在 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 主机名上的
/token
端点,例如https://instance_name.looker.com:19999
。API 主机名可能与 Looker 界面主机名相同或不同。/token
端点仅存在于 Looker API 主机中,/auth
端点仅存在于 Looker 界面主机中。 - 如果传递给
/token
端点的授权代码有效,Looker 会返回针对 OAuth 客户端应用网域的 CORS API 请求启用的 APIaccess_token
。
注册 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 }
以上示例代码依赖浏览器提供的
SubtleCrypto
API,但允许浏览器通过安全 (HTTPS) 网页使用这些功能。Looker 会尝试使用已配置 Looker 实例的身份验证系统来对用户进行身份验证。
- 如果用户在当前浏览器中已登录 Looker(也就是说,已经存在实时登录 Cookie 状态),系统将不会提示用户输入登录凭据。
- 如果这是您首次使用此 OAuth 客户端应用登录,Looker 会显示披露声明和确认页面,以供用户确认并接受。系统会显示注册应用时所使用的
description
参数中的文本。此说明应指明应用打算使用用户的 Looker 帐号。当用户点击 accept 时,页面会重定向到应用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
端点发出网络请求,并传递授权代码和您的应用信息。例如: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 管理员可以随时撤消此系统中的所有令牌。