使用 OAuth 的 Looker API 身份验证

Looker 使用 OAuth 让 OAuth 客户端应用在 Looker API 中进行身份验证,而不会向运行 OAuth 客户端应用的浏览器公开客户端 ID 和客户端密钥。

使用 OAuth 的 Web 应用必须满足以下要求:

  • 使用 OAuth 进行身份验证的功能仅适用于 Looker API 4.0
  • OAuth 客户端应用必须先使用 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 身份验证流程概览如下:

  1. 向 Looker API 注册 OAuth 客户端应用
  2. 将 OAuth 客户端应用的来源添加到嵌入式网域许可名单,以便进行代码交换 API 调用以及任何后续 CORS API 调用。
  3. 当 OAuth 客户端应用尝试对用户进行身份验证时,将浏览器网址重定向到 Looker 界面主机名(而不是 Looker API 主机名)上的 /auth 端点。例如 https://instance_name.looker.com
  4. 如果用户成功通过身份验证并登录 Looker,Looker 会立即向 OAuth 客户端应用返回 OAuth 重定向。如果用户尚未在当前设备和浏览器中登录 Looker,系统会显示 Looker 登录屏幕,并提示用户使用常规身份验证协议登录 Looker 用户帐号。
  5. 通过使用 OAuth 重定向中返回的授权代码,您的 OAuth 客户端应用接下来应该调用 Looker API 主机名上的 /token 端点,例如 https://instance_name.looker.com:19999。API 主机名可能与 Looker 界面主机名相同或不同。/token 端点仅存在于 Looker API 主机中,/auth 端点仅存在于 Looker 界面主机中。
  6. 如果传递给 /token 端点的授权代码有效,Looker 会返回针对 OAuth 客户端应用网域的 CORS API 请求启用的 API access_token

注册 OAuth 客户端应用

每个想要使用 OAuth 向 Looker API 进行身份验证的 OAuth 客户端应用都必须先向 Looker 实例注册,然后 Looker 才会授予访问权限。如需注册 OAuth 客户端应用,请执行以下操作:

  1. 在 Looker 实例上打开 API Explorer
  2. 使用版本下拉菜单,选择 4.0 -稳定版 API。
  3. 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:应用的全局唯一 ID
    • redirect_uri:应用将在其中收到包含授权代码的 OAuth 重定向的 URI
    • display_name:面向应用用户显示的应用的名称
    • description:应用的说明,会在用户首次从应用中登录时在披露声明和确认页面上向用户显示

    client_guidredirect_uri 参数的值必须与 OAuth 客户端应用确切提供的值一致,否则系统会拒绝通过身份验证。

使用 OAuth 执行用户登录

  1. 将用户转到界面主机上的 /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 登录可以即时完成,不会造成视觉中断。
  2. Looker API 将返回指向 OAuth 客户端应用的 OAuth 重定向。保存 URI 参数中列出的授权代码。以下是 OAuth 重定向 URI 示例:

    https://mywebapp.com:3000/authenticated?&code=asdfasdfassdf&state=...
    

    授权代码显示在 URI 中的 &code= 之后。在上面的示例中,授权代码为 asdfasdfassdf

  3. 在 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 管理员可以随时撤消此系统中的所有令牌。