为自定义客户端配置 Snowflake OAuth

本主题介绍如何为自定义客户端配置 OAuth 支持。

本主题内容:

工作流程

需要执行以下高级步骤才能为自定义客户端配置 OAuth :

  1. 向 Snowflake 注册您的客户端。要注册您的客户端,请创建一个集成。集成是一个 Snowflake 对象,它在 Snowflake 和第三方服务(例如支持 OAuth 的客户端)之间提供接口。

    注册过程定义了客户端 ID 和客户端密钥。

  2. 配置对 Snowflake OAuth 端点的调用,以便从 Snowflake 授权服务器请求授权代码,以及请求和刷新访问令牌。

    初始授权请求中的可选“scope”参数限制访问令牌允许的角色,并且还可用于配置刷新令牌行为。

备注

Snowflake OAuth 不支持在会话中将角色切换为辅助角色。

如果 OAuth 工作流程需要进行此行为,请改用外部 OAuth。

有关更多信息,请参阅 将次要角色与 External OAuth 结合使用

创建 Snowflake OAuth 集成

使用 CREATE SECURITY INTEGRATION 命令创建 Snowflake OAuth 集成。请务必在创建集成时指定 OAUTH_CLIENT = CUSTOM

备注

只有账户管理员(具有 ACCOUNTADMIN 角色的用户)或具有全局 CREATE INTEGRATION 权限的角色才能执行此 SQL 命令。

阻止特定角色使用集成

可选的 BLOCKED_ROLES_LIST 参数允许您列出用户 无法 明确同意在集成中使用的 Snowflake 角色。

默认情况下,ACCOUNTADMIN、SECURITYADMIN 和 ORGADMIN 角色包含在此列表中,并且无法移除。如果在业务上需要允许用户将 Snowflake OAuth 与这些角色一起使用,并且安全团队允许这样做,请联系 Snowflake 支持部门 (https://community.snowflake.com/s/article/How-To-Submit-a-Support-Case-in-Snowflake-Lodge),以请求允许您的账户使用这些角色。

将 Client Redirect 与 Snowflake OAuth 自定义客户端结合使用

Snowflake 支持将 Client Redirect 与 Snowflake OAuth 自定义客户端结合使用,包括将 Client Redirect 和 OAuth 与支持的 Snowflake 客户端结合使用。

有关更多信息,请参阅 重定向客户端连接

管理网络策略

Snowflake 支持将网络策略用于 OAuth。有关更多信息,请参阅 网络策略

集成示例

以下示例创建了一个使用密钥对身份验证的 OAuth 集成。该集成允许刷新令牌,这些令牌将在 1 天(86400 秒)后过期。该集成会阻止用户在使用 SYSADMIN 作为活动角色的情况下启动会话:

CREATE SECURITY INTEGRATION oauth_kp_int
  TYPE = OAUTH
  ENABLED = TRUE
  OAUTH_CLIENT = CUSTOM
  OAUTH_CLIENT_TYPE = 'CONFIDENTIAL'
  OAUTH_REDIRECT_URI = 'https://localhost.com'
  OAUTH_ISSUE_REFRESH_TOKENS = TRUE
  OAUTH_REFRESH_TOKEN_VALIDITY = 86400
  BLOCKED_ROLES_LIST = ('SYSADMIN')
  OAUTH_CLIENT_RSA_PUBLIC_KEY ='
  MIIBI
  ...
  ';
Copy

调用 OAuth 端点

OAuth 端点是客户端为请求授权代码以及请求和刷新访问令牌而调用的 URLs。这些端点是指在调用端点时执行的特定 OAuth 2.0 策略。

Snowflake 提供了以下 OAuth 端点:

授权:

<snowflake_account_url>/oauth/authorize

令牌请求:

<snowflake_account_url>/oauth/token-request

其中, <snowflake_account_url> 是有效的 Snowflake 账户 URL。例如,您可以使用端点 https://myorg-account_xyz.snowflakecomputing.cn/oauth/authorizehttps://myorg-account_xyz.snowflakecomputing.cn/oauth/token-request。有关 Snowflake 账户 URL 支持的格式的列表,请参阅 与 URL 连接

要查看安全集成的有效 OAuth 端点列表,请执行 DESCRIBE INTEGRATION,然后查看 OAUTH_ALLOWED_AUTHORIZATION_ENDPOINTSOAUTH_ALLOWED_TOKEN_ENDPOINTS 属性中的值。

授权端点

授权端点用于在用户使用 Snowflake 成功授权客户端后获取授权。

重要

必须 在用户可与之交互的浏览器中打开授权端点。不要将 cURL 与此端点结合使用。

授权端点如下所示:

<snowflake_account_url>/oauth/authorize
Copy

其中:

snowflake_account_url

指定有效的 Snowflake 账户 URL。例如 https://myorg-account_xyz.snowflakecomputing.cn/oauth/authorize

HTTP 方法

GET

查询参数

备注

应对以下参数进行 URL 编码。

参数

数据类型

是否必填?

描述

client_id

字符串

客户端 ID(在注册客户端时由 Snowflake 提供)

response_type

字符串

已创建的响应类型。目前支持 code 值,因为 Snowflake 仅发布授权代码。

redirect_uri

字符串

用户成功授权后被重定向到的 URI。通常,这应该与安全集成的 OAUTH_REDIRECT_URI 参数值匹配。

但是,如果 redirect_uri 包含查询参数,则在定义安全集成的 OAUTH_REDIRECT_URI 参数时不要包含这些查询参数。例如,如果在对授权端点的请求中, redirect_uri 查询参数的值为 https://www.example.com/connect?authType=snowflake,请确保将安全集成中的 OAUTH_REDIRECT_URI 参数设置为 https://www.example.com/connect

state

字符串

随 Snowflake 授权服务器的响应一起返回的字符串(不超过 2048 个 ASCII 字符)。通常用于防止跨站点请求伪造攻击。

scope

字符串

用于限制访问请求范围的空格分隔字符串。有关更多信息,请参阅 范围 (本主题内容)。

code_challenge

字符串

代码交换证明密钥 (PKCE) 质询。通过密钥和代码质询方法生成的字符串。有关更多信息,请参阅 代码交换证明密钥 (本主题内容)。

code_challenge_method

字符串

指示用于派生 PKCE 代码质询的方法的字符串。有关更多信息,请参阅 代码交换证明密钥 (本主题内容)。

当用户授权客户端时,将重定向到在 GET 请求中包含以下内容的 redirect_uri

查询参数

描述

code

短期授权代码,可以在令牌端点处交换为访问令牌。

state

原始请求中提供的 state 值,未修改。

scope

访问请求的范围;目前与初始授权请求中的 scope 值相同,但将来可能会有所不同。有关更多信息,请参阅 范围 (本主题内容)。

范围

初始授权请求中的 scope 查询参数可以选择限制访问令牌允许的操作和角色。

当发出语义方面的授权请求时,范围会立即得到验证,但不一定是有效性。也就是说,在用户进行身份验证之前,任何无效范围(例如“bogus_scope”)都会被拒绝,但用户无权访问的范围(特定角色等)在用户进行身份验证之前不会导致错误。

以下是 scope 查询参数的可能值:

范围值

是否必填?

描述

refresh_token

如果包含在授权 URL 中,Snowflake 会向用户提供同意脱机访问的选项。在这种情况下,脱机访问是指允许客户端在用户不在场时刷新访问令牌。在用户同意的情况下,授权服务器在兑换授权代码时除了返回访问令牌外,还会返回刷新令牌。

session:role:role_name

用于将访问令牌限制为用户可以同意会话的 单个 角色。只能指定一个会话角色范围。如果省略此范围,则改用用户的默认角色。当用户授权同意时,无论此范围是否包含在授权 URL 中,Snowflake 始终显示会话的角色。

请注意, role_name 区分大小写,并且必须以全部大写形式输入,除非在使用 CREATE ROLE 创建角色名称时用引号将角色名称括起来。要验证这种情况,请在 Snowflake 中执行 SHOW ROLES 并查看输出中的角色名称。

如果角色名称包含查询参数 URL 中保留的字符,则必须使用 session:role-encoded:role_name 语法,其中 role_name 是 URL 编码的字符串。例如,如果角色名称为 AUTH SNOWFLAKE``(带空格),则 :code:`scope` 查询参数的值必须为 ``session:role-encoded:AUTH%20SNOWFLAKE

以下示例限制对自定义 R1 角色的授权:

scope=session:role:R1
Copy

以下示例指示访问/刷新令牌应使用用户的默认角色,并请求刷新令牌,以便可以进行脱机访问:

scope=refresh_token
Copy

以下示例限制对自定义 R1 角色的授权并请求刷新令牌,以便可以进行脱机访问:

scope=refresh_token session:role:R1
Copy

令牌端点

此端点根据请求参数返回访问令牌或刷新令牌。令牌端点如下所示:

<snowflake_account_url>/oauth/token-request
Copy

其中:

snowflake_account_url

指定有效的 Snowflake 账户 URL。例如 https://myorg-account_xyz.snowflakecomputing.cn/oauth/token-request

HTTP 方法

POST

确保 POST 请求中的 content-type 标头设置如下:

Content-type: application/x-www-form-urlencoded
Copy

请求标头

客户端 ID 和客户端密钥必须包含在授权标头中。目前,Snowflake仅支持 基本身份验证方案 (https://tools.ietf.org/html/rfc2617),这意味着预期的值采用以下形式:

Basic Base64(client_id:client_secret)

其中:

标头值

数据类型

必填

描述

client_id

字符串

集成的客户端 ID。

client_secret

字符串

集成的客户端密钥。

可以使用 SYSTEM$SHOW_OAUTH_CLIENT_SECRETS 函数检索客户端 ID 和客户端密钥。

请注意 client_idclient_secret 之间的 : 字符。

请求正文

参数

数据类型

必填

描述

grant_type

字符串

请求的授权类型: . authorization_code 表示应将授权代码交换为访问令牌。. refresh_token 表示刷新访问令牌的请求。

code

字符串

从令牌端点返回的授权代码。在 grant_type 设为 authorization_code 时使用且必须使用。

refresh_token

字符串

在兑换授权代码时,刷新从先前请求返回到令牌端点的令牌。在 grant_type 设为 refresh_token 时使用且必须使用。

redirect_uri

字符串

请求授权代码时在授权 URL 中使用的重定向 URI。在 grant_type 设为 authorization_code 时使用且必须使用。

code_verifier

字符串

仅当授权请求使用 code_challenge 参数值发送到 授权端点 时才需要。PKCE 的代码验证器。有关更多信息,请参阅 代码交换证明密钥 (本主题内容)。

响应

返回的 JSON 对象包含以下字段:

字段

数据类型

描述

access_token

字符串

用于建立 Snowflake 会话的访问令牌

refresh_token

字符串

刷新令牌。如果客户端配置为不颁发刷新令牌,或者用户不同意 refresh_token 范围,则不会颁发刷新令牌。

expires_in

整数

令牌过期前的剩余秒数

token_type

字符串

访问令牌类型。目前,总是 Bearer

username

字符串

访问令牌所属的用户名。目前仅在将授权代码交换为访问令牌时返回。

成功响应示例

以下示例显示了将授权代码交换为访问和刷新令牌时的成功响应:

{
  "access_token":  "ACCESS_TOKEN",
  "expires_in": 600,
  "refresh_token": "REFRESH_TOKEN",
  "token_type": "Bearer",
  "username": "user1",
}
Copy
不成功的响应示例

以下示例显示了不成功的响应:

{
  "data" : null,
  "message" : "This is an invalid client.",
  "code" : null,
  "success" : false,
  "error" : "invalid_client"
}
Copy

message 字符串值是错误的描述, error 是错误类型。有关返回的错误类型的更多信息,请参阅 OAuth 错误代码

代码交换证明密钥

Snowflake 支持使用代码交换证明密钥 (PKCE) 通过 authorization_code 授予类型获取访问令牌,如 RFC 7636 (https://tools.ietf.org/html/rfc7636) 中所述。PKCE 可用于减少授权代码拦截攻击的可能性,并且适用于可能无法完全保护客户端密钥安全的客户端。

默认情况下,PKCE 是可选的,并且仅当 code_challengecode_challenge_method 参数都包含在授权端点 URL 中时才强制执行。但是,Snowflake 强烈 建议客户端要求所有授权都使用 PKCE,以使 OAuth 流程更加安全。

下面介绍了 Snowflake 的 PKCE 的工作原理:

  1. 客户端创建一个名为 代码验证器 的密钥,并对其执行转换以生成 代码质询。客户保留该密钥。

    重要

    根据 ` RFC 7636 的第 4.1 节 <https://tools.ietf.org/html/rfc7636#section-4.1 (https://tools.ietf.org/html/rfc7636#section-4.1)> `_,从允许的 ASCII 字符生成*code verifier*。

  2. 将用户定向到授权 URL 的客户端附加以下两个查询参数:

    code_challenge

    指定在第 1 步中生成的代码质询。

    code_challenge_method

    指定在第 1 步中用于生成代码质询的代码验证器上的转换。目前,Snowflake 仅支持 SHA256,因此此值必须设置为 S256。SHA256 的转换算法是 BASE64URL-ENCODE(SHA256(ASCII(code_verifier)))

  3. 在用户同意所请求的范围或 Snowflake 确定该用户同意后,将发出授权代码。

  4. 客户端从 Snowflake 授权服务器接收授权代码,然后将其与请求中的 code_verifier 一起提交到令牌端点。

  5. Snowflake 转换 code_verifier 值并验证转换后的值是否与生成授权时使用的 code_challenge 值匹配。如果这些值匹配,则授权服务器发出访问令牌和刷新令牌。

使用密钥对身份验证

Snowflake 支持在调用 OAuth 令牌端点时使用密钥对身份验证,而不使用典型的用户名/密码身份验证。此身份验证方法需要 2048 位(最低)RSA 密钥对。使用 OpenSSL 生成 PEM(隐私增强邮件)公私密钥对。公钥分配给使用 Snowflake 客户端的 Snowflake 用户。

要配置公钥/私钥对,请执行以下操作:

  1. 在终端窗口的命令行中,生成加密的私钥:

    $ openssl genrsa 2048 | openssl pkcs8 -topk8 -v2 des3 -inform PEM -out rsa_key.p8
    
    Copy

    OpenSSL 提示输入用于加密私钥文件的密码。Snowflake 建议使用强密码来保护私钥。记录此密码。您必须在连接到 Snowflake 时输入此密码。请注意,密码仅用于保护私钥,永远不会发送到 Snowflake。

    示例 PEM 私钥

    -----BEGIN ENCRYPTED PRIVATE KEY-----
    MIIE6TAbBgkqhkiG9w0BBQMwDgQILYPyCppzOwECAggABIIEyLiGSpeeGSe3xHP1
    wHLjfCYycUPennlX2bd8yX8xOxGSGfvB+99+PmSlex0FmY9ov1J8H1H9Y3lMWXbL
    ...
    -----END ENCRYPTED PRIVATE KEY-----
    
    Copy
  2. 从命令行中,通过引用私钥生成公钥:

    $ openssl rsa -in rsa_key.p8 -pubout -out rsa_key.pub
    
    Copy

    示例 PEM 公钥

    -----BEGIN PUBLIC KEY-----
    MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAy+Fw2qv4Roud3l6tjPH4
    zxybHjmZ5rhtCz9jppCV8UTWvEXxa88IGRIHbJ/PwKW/mR8LXdfI7l/9vCMXX4mk
    ...
    -----END PUBLIC KEY-----
    
    Copy
  3. 将公钥和私钥文件复制到本地目录进行存储。记录文件的路径。

    请注意,私钥使用 PKCS#8(公钥加密标准)格式存储,并使用您在上一步中指定的加密短语进行加密;但仍应使用操作系统提供的文件权限机制保护文件,防止未经授权的访问。您有责任在不使用文件时保护该文件。

  4. 使用 ALTER SECURITY INTEGRATION 将公钥分配给集成对象。例如:

    ALTER SECURITY INTEGRATION myint SET OAUTH_CLIENT_RSA_PUBLIC_KEY='MIIBIjANBgkqh...';
    
    Copy

    备注

    • 只有账户管理员才能执行 ALTER SECURITY INTEGRATION 命令。

    • 排除该命令中的公钥标头和页脚。

    使用 DESCRIBE INTEGRATION 验证公钥指纹:

    DESC SECURITY INTEGRATION myint;
    
    +----------------------------------+---------------+----------------------------------------------------------------------+------------------+
    | property                         | property_type | property_value                                                       | property_default |
    |----------------------------------+---------------+----------------------------------------------------------------------+------------------|
    ...
    | OAUTH_CLIENT_RSA_PUBLIC_KEY_FP   | String        | SHA256:MRItnbO/123abc/abcdefghijklmn12345678901234=                  |                  |
    | OAUTH_CLIENT_RSA_PUBLIC_KEY_2_FP | String        |                                                                      |                  |
    ...
    +----------------------------------+---------------+----------------------------------------------------------------------+------------------+
    
    Copy

    备注

    密钥轮换 (本主题内容)中介绍了 OAUTH_CLIENT_RSA_PUBLIC_KEY_2_FP 属性。

  5. 修改并执行下面的示例代码。该代码使用私钥对 JWT 进行编码,然后将该令牌传递给 Snowflake 授权服务器:

    • 更新安全参数:

      • <private_key>:在文本编辑器中打开 rsa_key.p8 文件,然后复制 BEGIN 页眉和 END 页脚之间的行。

    • 更新会话参数:

      • <account_identifier>:指定账户的全名(由 Snowflake 提供)。

    • 更新 JSON Web 令牌 (JWT) 字段:

      post body

      具有以下标准字段(“claims”)的 JSON 对象:

      属性

      数据类型

      必填

      描述

      iss

      字符串

      指定以 client_id.public_key_fp 格式发出 JWT 主体,其中 client_id 是 OAuth 客户端集成的客户端 ID,public_key_fp 是验证期间使用的公钥的指纹。

      sub

      字符串

      JWT 的主题采用 account_identifier.client_id 格式,其中 account_identifier 是 Snowflake 账户的全名, client_id 是 OAuth 客户端集成的客户端 ID。根据托管账户的云平台(AWS 或 Azure)和区域,完整账户名称可能需要 其他 段。有关更多信息,请参阅 令牌端点 下的 account 变量说明。

      iat

      时间戳

      令牌的发放时间。

      exp

      时间戳

      令牌的到期时间。这段时间应该相对较短(例如几分钟)。

    示例代码

    请注意, private_key 值包括 -----BEGIN 页眉和 -----END 页脚。

    import datetime
    import json
    import urllib
    
    import jwt
    import requests
    
    private_key = """
    <private_key>
    """
    
    public_key_fp = "SHA256:MR..."
    
    
    def _make_request(payload, encoded_jwt_token):
        token_url = "https://<account_identifier>.snowflakecomputing.cn/oauth/token-request"
        headers = {
                u'Authorization': "Bearer %s" % (encoded_jwt_token),
                u'content-type': u'application/x-www-form-urlencoded'
        }
        r = requests.post(
                token_url,
                headers=headers,
                data=urllib.urlencode(payload))
        return r.json()
    
    
    def make_request_for_access_token(oauth_az_code, encoded_jwt_token):
        """ Given an Authorization Code, make a request for an Access Token
        and a Refresh Token."""
        payload = {
            'grant_type': 'authorization_code',
            'code': oauth_az_code
        }
        return _make_request(payload, encoded_jwt_token)
    
    
    def make_request_for_refresh_token(refresh_token, encoded_jwt_token):
        """ Given a Refresh Token, make a request for another Access Token."""
        payload = {
            'grant_type': 'refresh_token',
            'refresh_token': refresh_token
        }
        return _make_request(payload, encoded_jwt_token)
    
    
    def main():
        account_identifier = "<account_identifier>"
        client_id = "1234"  # found by running DESC SECURITY INTEGRATION
        issuer = "{}.{}".format(client_id, public_key_fp)
        subject = "{}.{}".format(account_identifier, client_id)
        payload = {
            'iss': issuer,
            'sub': subject,
            'iat': datetime.datetime.utcnow(),
            'exp': datetime.datetime.utcnow() + datetime.timedelta(seconds=30)
        }
        encoded_jwt_token = jwt.encode(
                payload,
                private_key,
                algorithm='RS256')
    
        data = make_request_for_access_token(oauth_az_code, encoded_jwt_token)
        refresh_token = data['refresh_token']
        data = make_request_for_refresh_token(refresh_token, encoded_jwt_token)
        access_token = data['access_token']
    
    
    if __name__ == '__main__':
        main()
    
    Copy

    创建令牌后,在请求中将令牌提交到令牌端点。请求需要持有者授权格式作为授权标头,而不是通常用于客户端 ID 和客户端密钥的基本授权格式,如下所示:

    "Authorization: Bearer JWT_TOKEN"
    
    Copy

密钥轮换

Snowflake 支持多个活动键以实现不间断轮换。根据内部遵循的到期时间表轮换和替换公钥和私钥。

目前,您可以使用 ALTER SECURITY INTEGRATIONOAUTH_CLIENT_RSA_PUBLIC_KEYOAUTH_CLIENT_RSA_PUBLIC_KEY_2 参数将最多 2 个公钥与单个用户关联。

要轮换密钥,请执行以下操作:

  1. 完成 使用密钥对身份验证 (本主题内容)中的步骤:

    • 生成新的私钥和公钥集。

    • 将公钥分配给集成。将公钥值设置为 OAUTH_CLIENT_RSA_PUBLIC_KEY 或者 :samp:`OAUTH_CLIENT_RSA_PUBLIC_KEY_2`(以当前未使用的密钥值为准)。例如:

      alter integration myint set oauth_client_rsa_public_key_2='JERUEHtcve...';
      
      Copy
  2. 更新代码以连接到 Snowflake。指定新的私钥。

    Snowflake 根据提交的私钥来验证用于身份验证的活动公钥是否正确无误。

  3. 从集成中删除旧的公钥。例如:

    alter integration myint unset oauth_client_rsa_public_key;
    
    Copy

错误代码

请参阅 错误代码,以查看与 OAuth 关联的错误代码列表,以及在授权流程、令牌请求或交换期间或在完成 OAuth 流程后创建 Snowflake 会话时,在 JSON Blob 中返回的错误。

语言: 中文