为自定义客户端配置 Snowflake OAuth¶
本主题介绍如何为自定义客户端配置 OAuth 支持。
本主题内容:
工作流程¶
需要执行以下高级步骤才能为自定义客户端配置 OAuth :
向 Snowflake 注册您的客户端。要注册您的客户端,请创建一个集成。集成是一个 Snowflake 对象,它在 Snowflake 和第三方服务(例如支持 OAuth 的客户端)之间提供接口。
注册过程定义了客户端 ID 和客户端密钥。
配置对 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
...
';
调用 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/authorize
和 https://myorg-account_xyz.snowflakecomputing.cn/oauth/token-request
。有关 Snowflake 账户 URL 支持的格式的列表,请参阅 与 URL 连接。
要查看安全集成的有效 OAuth 端点列表,请执行 DESCRIBE INTEGRATION,然后查看 OAUTH_ALLOWED_AUTHORIZATION_ENDPOINTS
和 OAUTH_ALLOWED_TOKEN_ENDPOINTS
属性中的值。
令牌端点¶
此端点根据请求参数返回访问令牌或刷新令牌。令牌端点如下所示:
<snowflake_account_url>/oauth/token-request
其中:
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
请求标头¶
客户端 ID 和客户端密钥必须包含在授权标头中。目前,Snowflake仅支持 基本身份验证方案 (https://tools.ietf.org/html/rfc2617),这意味着预期的值采用以下形式:
Basic Base64(client_id:client_secret)
其中:
标头值 |
数据类型 |
必填 |
描述 |
---|---|---|---|
|
字符串 |
是 |
集成的客户端 ID。 |
|
字符串 |
是 |
集成的客户端密钥。 |
可以使用 SYSTEM$SHOW_OAUTH_CLIENT_SECRETS 函数检索客户端 ID 和客户端密钥。
请注意 client_id
和 client_secret
之间的 :
字符。
请求正文¶
参数 |
数据类型 |
必填 |
描述 |
---|---|---|---|
|
字符串 |
是 |
请求的授权类型: . |
|
字符串 |
是 |
从令牌端点返回的授权代码。在 |
|
字符串 |
是 |
在兑换授权代码时,刷新从先前请求返回到令牌端点的令牌。在 |
|
字符串 |
是 |
请求授权代码时在授权 URL 中使用的重定向 URI。在 |
|
字符串 |
否 |
仅当授权请求使用 |
响应¶
返回的 JSON 对象包含以下字段:
字段 |
数据类型 |
描述 |
---|---|---|
|
字符串 |
用于建立 Snowflake 会话的访问令牌 |
|
字符串 |
刷新令牌。如果客户端配置为不颁发刷新令牌,或者用户不同意 |
|
整数 |
令牌过期前的剩余秒数 |
|
字符串 |
访问令牌类型。目前,总是 |
|
字符串 |
访问令牌所属的用户名。目前仅在将授权代码交换为访问令牌时返回。 |
成功响应示例¶
以下示例显示了将授权代码交换为访问和刷新令牌时的成功响应:
{
"access_token": "ACCESS_TOKEN",
"expires_in": 600,
"refresh_token": "REFRESH_TOKEN",
"token_type": "Bearer",
"username": "user1",
}
不成功的响应示例¶
以下示例显示了不成功的响应:
{
"data" : null,
"message" : "This is an invalid client.",
"code" : null,
"success" : false,
"error" : "invalid_client"
}
message
字符串值是错误的描述, error
是错误类型。有关返回的错误类型的更多信息,请参阅 OAuth 错误代码。
代码交换证明密钥¶
Snowflake 支持使用代码交换证明密钥 (PKCE) 通过 authorization_code
授予类型获取访问令牌,如 RFC 7636 (https://tools.ietf.org/html/rfc7636) 中所述。PKCE 可用于减少授权代码拦截攻击的可能性,并且适用于可能无法完全保护客户端密钥安全的客户端。
默认情况下,PKCE 是可选的,并且仅当 code_challenge
和 code_challenge_method
参数都包含在授权端点 URL 中时才强制执行。但是,Snowflake 强烈 建议客户端要求所有授权都使用 PKCE,以使 OAuth 流程更加安全。
下面介绍了 Snowflake 的 PKCE 的工作原理:
客户端创建一个名为 代码验证器 的密钥,并对其执行转换以生成 代码质询。客户保留该密钥。
重要
根据 ` 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*。
将用户定向到授权 URL 的客户端附加以下两个查询参数:
code_challenge
指定在第 1 步中生成的代码质询。
code_challenge_method
指定在第 1 步中用于生成代码质询的代码验证器上的转换。目前,Snowflake 仅支持 SHA256,因此此值必须设置为
S256
。SHA256 的转换算法是BASE64URL-ENCODE(SHA256(ASCII(code_verifier)))
。
在用户同意所请求的范围或 Snowflake 确定该用户同意后,将发出授权代码。
客户端从 Snowflake 授权服务器接收授权代码,然后将其与请求中的
code_verifier
一起提交到令牌端点。Snowflake 转换
code_verifier
值并验证转换后的值是否与生成授权时使用的code_challenge
值匹配。如果这些值匹配,则授权服务器发出访问令牌和刷新令牌。
使用密钥对身份验证¶
Snowflake 支持在调用 OAuth 令牌端点时使用密钥对身份验证,而不使用典型的用户名/密码身份验证。此身份验证方法需要 2048 位(最低)RSA 密钥对。使用 OpenSSL 生成 PEM(隐私增强邮件)公私密钥对。公钥分配给使用 Snowflake 客户端的 Snowflake 用户。
要配置公钥/私钥对,请执行以下操作:
在终端窗口的命令行中,生成加密的私钥:
$ openssl genrsa 2048 | openssl pkcs8 -topk8 -v2 des3 -inform PEM -out rsa_key.p8
OpenSSL 提示输入用于加密私钥文件的密码。Snowflake 建议使用强密码来保护私钥。记录此密码。您必须在连接到 Snowflake 时输入此密码。请注意,密码仅用于保护私钥,永远不会发送到 Snowflake。
示例 PEM 私钥
-----BEGIN ENCRYPTED PRIVATE KEY----- MIIE6TAbBgkqhkiG9w0BBQMwDgQILYPyCppzOwECAggABIIEyLiGSpeeGSe3xHP1 wHLjfCYycUPennlX2bd8yX8xOxGSGfvB+99+PmSlex0FmY9ov1J8H1H9Y3lMWXbL ... -----END ENCRYPTED PRIVATE KEY-----
从命令行中,通过引用私钥生成公钥:
$ openssl rsa -in rsa_key.p8 -pubout -out rsa_key.pub
示例 PEM 公钥
-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAy+Fw2qv4Roud3l6tjPH4 zxybHjmZ5rhtCz9jppCV8UTWvEXxa88IGRIHbJ/PwKW/mR8LXdfI7l/9vCMXX4mk ... -----END PUBLIC KEY-----
将公钥和私钥文件复制到本地目录进行存储。记录文件的路径。
请注意,私钥使用 PKCS#8(公钥加密标准)格式存储,并使用您在上一步中指定的加密短语进行加密;但仍应使用操作系统提供的文件权限机制保护文件,防止未经授权的访问。您有责任在不使用文件时保护该文件。
使用 ALTER SECURITY INTEGRATION 将公钥分配给集成对象。例如:
ALTER SECURITY INTEGRATION myint SET OAUTH_CLIENT_RSA_PUBLIC_KEY='MIIBIjANBgkqh...';
备注
只有账户管理员才能执行 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 | | | ... +----------------------------------+---------------+----------------------------------------------------------------------+------------------+
备注
密钥轮换 (本主题内容)中介绍了
OAUTH_CLIENT_RSA_PUBLIC_KEY_2_FP
属性。修改并执行下面的示例代码。该代码使用私钥对 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()
创建令牌后,在请求中将令牌提交到令牌端点。请求需要持有者授权格式作为授权标头,而不是通常用于客户端 ID 和客户端密钥的基本授权格式,如下所示:
"Authorization: Bearer JWT_TOKEN"
密钥轮换¶
Snowflake 支持多个活动键以实现不间断轮换。根据内部遵循的到期时间表轮换和替换公钥和私钥。
目前,您可以使用 ALTER SECURITY INTEGRATION 的 OAUTH_CLIENT_RSA_PUBLIC_KEY
和 OAUTH_CLIENT_RSA_PUBLIC_KEY_2
参数将最多 2 个公钥与单个用户关联。
要轮换密钥,请执行以下操作:
完成 使用密钥对身份验证 (本主题内容)中的步骤:
生成新的私钥和公钥集。
将公钥分配给集成。将公钥值设置为
OAUTH_CLIENT_RSA_PUBLIC_KEY
或者 :samp:`OAUTH_CLIENT_RSA_PUBLIC_KEY_2`(以当前未使用的密钥值为准)。例如:alter integration myint set oauth_client_rsa_public_key_2='JERUEHtcve...';
更新代码以连接到 Snowflake。指定新的私钥。
Snowflake 根据提交的私钥来验证用于身份验证的活动公钥是否正确无误。
从集成中删除旧的公钥。例如:
alter integration myint unset oauth_client_rsa_public_key;
错误代码¶
请参阅 错误代码,以查看与 OAuth 关联的错误代码列表,以及在授权流程、令牌请求或交换期间或在完成 OAuth 流程后创建 Snowflake 会话时,在 JSON Blob 中返回的错误。