OAuth 2.0 身份认证 API

极狐GitLab 提供 API 以允许第三方服务代理用户访问极狐GitLab 资源,该过程使用 OAuth 2.0 协议。

要为此配置极狐GitLab,请参阅将极狐GitLab 配置为 OAuth 2.0 身份验证提供者

此功能基于 doorkeeper Ruby gem

跨域资源共享

CORS 预检请求支持引入于极狐GitLab 15.1。

许多 /oauth 端点支持跨域资源共享 (CORS)。从极狐GitLab 15.1 开始,以下端点也支持 CORS 预检请求

  • /oauth/撤销
  • /oauth/token
  • /oauth/userinfo

只有某些 header 可用于预检请求:

  • 简单请求列出的 header。
  • Authorization header。

例如,X-Requested-With header 不能用于预检请求。

支持的 OAuth 2.0 流程

极狐GitLab 支持以下授权流程:

  • 带有 Proof Key for Code Exchange (PKCE) 的授权码: 最安全,推荐用于客户端和服务器应用程序。如果没有 PKCE,您必须在客户端上保存密钥信息。
  • 授权码: 安全而常见的流程。推荐用于部署于安全环境的服务器端应用程序。
  • 资源所有者密码证书: 仅用于安全托管的第一方服务。建议不要使用此流程。

不支持 Device Authorization Grant。

OAuth 2.1 的规范草案特别省略了隐式授权和资源所有者密码凭证流。

请参阅 OAuth RFC 了解所有这些流程如何工作并为您的用例选择合适的流程。

授权码(带或不带 PKCE)流程需要首先在您用户账户中的 /profile/applications 页面注册应用。 在注册过程中,通过设定适当权限范围,您可以限制应用可以访问的资源。 创建后,您将获得应用凭证:Application IDClient SecretClient Secret 必须安全存放。如果您的程序允许,Application ID 也应该视为秘密。

参阅将极狐GitLab 配置为 OAuth 2.0 身份验证提供者可查看可用的权限范围列表。

防御 CSRF 攻击

为了保护基于重定向的流程,OAuth 规范建议使用 CSRF 令牌参数,用于指向 /oauth/authorize 的请求,可以防止 CSRF 攻击

在生产环境中使用 HTTPS

对于生产,请为您的 redirect_uri 使用 HTTPS。在开发阶段,极狐GitLab 允许使用不安全的 HTTP 重定向 URI。

由于 OAuth 2.0 的安全性完全基于传输层,因此您不应使用未受保护的 URI。 有关详细信息,请参阅 OAuth 2.0 RFCOAuth 2.0 威胁模型 RFC

在接下来的部分,我们会讨论在各个流程中如何进行授权。

PKCE 授权码流程

PKCE 的 RFC 规范 包括了详细的流程描述,从授权请求到访问令牌。以下步骤描述了我们的流程实现。

带有PCKE授权码(下文记为 PKCE),可以在不依赖 Client Secret 的情况下,安全地在不受信任的客户端上进行 OAuth 访问凭据交换以获取访问令牌。这使得 PKCE 特别适合用于 JavaScript 单页应用,或者在客户端上难以安全保存凭据的其他场景。

在开始流程之前,生成STATECODE_VERIFIERCODE_CHALLENGE

  • STATE 是一个无法预测的值,客户端使用它来维护请求和回调之间的状态。它也应该用作 CSRF 令牌。
  • CODE_VERIFIER 是一个随机字符串,长度在 43 到 128 个字符之间,可用字符为A-Za-z0-9-._~
  • CODE_CHALLENGE 是一个 URL 安全的 Base64 编码的 CODE_VERIFIER SHA256 哈希。
    • SHA256 哈希在编码之前必须是二进制格式。
    • 在 Ruby 中,您可以使用 Base64.urlsafe_encode64(Digest::SHA256.digest(CODE_VERIFIER), padding: false) 进行设置。
    • 作为参考,CODE_VERIFIER 字符串 ks02i3jdikdo2k0dkfodf3m39rjfjsdk0wk349rj3jrhf 对应的 CODE_CHALLENGE 字符串为 2i0WFA-0AerkjQm4X4oDEhqA17QIAKNjXpagHBXmO_U
  1. 请求授权码。为此,您应该将用户重定向到带有以下查询参数的 /oauth/authorize 页面:

    https://gitlab.example.com/oauth/authorize?client_id=APP_ID&redirect_uri=REDIRECT_URI&response_type=code&state=STATE&scope=REQUESTED_SCOPES&code_challenge=CODE_CHALLENGE&code_challenge_method=S256
    

    此页面要求用户批准应用程序访问他们的账户,访问的资源范围由 REQUESTED_SCOPES 指定。 用户在同意后会被重定向回 REDIRECT_URI资源范围参数是空格分隔的列表。 例如,scope=read_user+profile 请求 read_userprofile 资源范围。 重定向时,URL 中会包括授权 code,形如:

    https://example.com/oauth/redirect?code=1234567890&state=STATE
    
  2. 使用上一步请求返回的授权 code(以下记为RETURNED_CODE),您可以请求使用任何 HTTP 客户端向极狐GitLab 请求一个 access_token。以下示例使用 Ruby 的 rest-client

    parameters = 'client_id=APP_ID&code=RETURNED_CODE&grant_type=authorization_code&redirect_uri=REDIRECT_URI&code_verifier=CODE_VERIFIER'
    RestClient.post 'https://gitlab.example.com/oauth/token', parameters
    

    响应示例:

    {
     "access_token": "de6780bc506a0446309bd9362820ba8aed28aa506c71eedbe1c5c4f9dd350e54",
     "token_type": "bearer",
     "expires_in": 7200,
     "refresh_token": "8257e65c97202ed1726cf9571600918f3bffb2544b26e00a61df9897668c33a1",
     "created_at": 1607635748
    }
    
  3. 使用 refresh_token 以获取新的 access_tokenrefresh_tokenaccess_token 过期后仍然有效。 下列的请求会做两件事:

    • 作废当前的 access_tokenrefresh_token
    • 返回新的 access_tokenrefresh_token
      parameters = 'client_id=APP_ID&refresh_token=REFRESH_TOKEN&grant_type=refresh_token&redirect_uri=REDIRECT_URI&code_verifier=CODE_VERIFIER'
      RestClient.post 'https://gitlab.example.com/oauth/token', parameters
    

    响应示例:

    {
      "access_token": "c97d1fe52119f38c7f67f0a14db68d60caa35ddc86fd12401718b649dcfa9c68",
      "token_type": "bearer",
      "expires_in": 7200,
      "refresh_token": "803c1fd487fec35562c205dac93e9d8e08f9d3652a24079d704df3039df1158f",
      "created_at": 1628711391
    }
    
note请求 access_token 时的 redirect_uri 必须与授权时提供的 redirect_uri 匹配。

您现在可以使用 access_token 请求 API 了。

应用授权码流程

note查看 RFC 规范 以获取详细的流程说明。

授权码流程与 PKCE 授权码流程大致相同。

在开始流程之前,生成STATE,它是一个无法预测的值,客户端使用它来维护请求和回调之间的状态。它也应该用作 CSRF 令牌。

  1. 请求授权码。为此,您应该将用户重定向到带有以下查询参数的 /oauth/authorize 页面:

    https://gitlab.example.com/oauth/authorize?client_id=APP_ID&redirect_uri=REDIRECT_URI&response_type=code&state=STATE&scope=REQUESTED_SCOPES
    

    此页面要求用户批准应用程序访问他们的账户,访问的资源范围由 REQUESTED_SCOPES 指定。 用户在同意后会被重定向回 REDIRECT_URI资源范围参数是空格分隔的列表。 例如,scope=read_user+profile 请求 read_userprofile 资源范围。 重定向时,URL 中会包括授权 code,形如:

    https://example.com/oauth/redirect?code=1234567890&state=STATE
    
  2. 使用上一步请求返回的授权 code(以下记为RETURNED_CODE),您可以请求使用任何 HTTP 客户端向极狐GitLab 请求一个 access_token。以下示例使用 Ruby 的 rest-client

    parameters = 'client_id=APP_ID&client_secret=APP_SECRET&code=RETURNED_CODE&grant_type=authorization_code&redirect_uri=REDIRECT_URI'
    RestClient.post 'https://gitlab.example.com/oauth/token', parameters
    

    响应示例:

    {
     "access_token": "de6780bc506a0446309bd9362820ba8aed28aa506c71eedbe1c5c4f9dd350e54",
     "token_type": "bearer",
     "expires_in": 7200,
     "refresh_token": "8257e65c97202ed1726cf9571600918f3bffb2544b26e00a61df9897668c33a1",
     "created_at": 1607635748
    }
    
  3. 使用 refresh_token 以获取新的 access_tokenrefresh_tokenaccess_token 过期后仍然有效。 下列的请求会做两件事:

    • 作废当前的 access_tokenrefresh_token
    • 返回新的 access_tokenrefresh_token
      parameters = 'client_id=APP_ID&client_secret=APP_SECRET&refresh_token=REFRESH_TOKEN&grant_type=refresh_token&redirect_uri=REDIRECT_URI'
      RestClient.post 'https://gitlab.example.com/oauth/token', parameters
    

    返回示例:

    {
      "access_token": "c97d1fe52119f38c7f67f0a14db68d60caa35ddc86fd12401718b649dcfa9c68",
      "token_type": "bearer",
      "expires_in": 7200,
      "refresh_token": "803c1fd487fec35562c205dac93e9d8e08f9d3652a24079d704df3039df1158f",
      "created_at": 1628711391
    }
    
note请求 access_token 时的 redirect_uri 必须与授权时提供的 redirect_uri 匹配。

您现在可以使用 access_token 请求 API 了。

用户密码流程

note请参阅 RFC 规范 获取详细的流程描述。
note启用了双重身份认证的用户无法使用自己的密码作为凭据进行此流程。这些用户可以转而使用个人访问令牌

在该流程中,用户需要提供用户名和密码以获取 access_token

本流程仅应在以下情况下使用:

  • 用户和客户端之间有着高度的信任,例如,客户端是操作系统的一部分,或者是一个具有特权的应用程序。
  • 其他授权流程不可用,例如应用授权码。
caution永远不要存储用户的密码,并且仅在您的客户端部署于安全环境时使用此授权类型。在 99% 的情况下,让用户提供个人访问令牌是一个更好的选择。

尽管本授权流程需要直接使用用户的用户名和密码,前述凭据只在一开始申请 access_token 时使用一次。 有了 access_tokenrefresh_token 后,客户端就可以使用它们来进行资源访问而无需存储用户凭据。

要申请 access_token,你需要发起一个到 /oauth/token 的 POST 请求,并且带上下列参数:

{
  "grant_type"    : "password",
  "username"      : "user@example.com",
  "password"      : "secret"
}

一个使用 cURL 的例子:

echo 'grant_type=password&username=<your_username>&password=<your_password>' > auth.txt
curl --data "@auth.txt" --request POST "https://gitlab.example.com/oauth/token"

你也可以以 OAuth 应用的身份发起该请求,此时将应用的client_idclient_secret 作为 HTTP Basic Authentication 的参数:

echo 'grant_type=password&username=<your_username>&password=<your_password>' > auth.txt
curl --data "@auth.txt" --user client_id:client_secret \
     --request POST "https://gitlab.example.com/oauth/token"

你会收到类似下方示例的返回:

{
  "access_token": "1f0af717251950dbd4d73154fdf0a474a5c5119adad999683f5b450c460726aa",
  "token_type": "bearer",
  "expires_in": 7200
}

默认情况下,access_token 的权限范围是 api,具有完全的读写权限。

下面是一个使用 oauth2 Ruby gem 的例子:

client = OAuth2::Client.new('the_client_id', 'the_client_secret', :site => "https://example.com")
access_token = client.password.get_token('user@example.com', 'secret')
puts access_token.token

使用 access token 访问 API

access token 能让您以用户的身份调用 API,您可以将其置于 GET 参数中:

GET https://gitlab.example.com/api/v4/user?access_token=OAUTH-TOKEN

也可以置于 Authorization header 中:

curl --header "Authorization: Bearer OAUTH-TOKEN" "https://gitlab.example.com/api/v4/user"

使用 access_token 访问 Git over HTTPS

具有 read_repositorywrite_repository 权限范围access token 可以作为密钥访问 Git over HTTPS,此时应当使用 oauth2 作为用户名,不使用用户自己的用户名:

https://oauth2:<your_access_token>@gitlab.example.com/project_path/project_name.git

获取 access_token 的详情

您可使用 /oauth/token/info API 获取 access_token 的详情。该功能由 Doorkeeper gem 提供。

调用接口时需要提供 access_token

  • 作为 URL 参数:

     GET https://gitlab.example.com/oauth/token/info?access_token=<OAUTH-TOKEN>
    
  • 置于 Authorization header 中:

     curl --header "Authorization: Bearer <OAUTH-TOKEN>" "https://gitlab.example.com/oauth/token/info"
    

响应示例:

{
    "resource_owner_id": 1,
    "scope": ["api"],
    "expires_in": null,
    "application": {"uid": "1cb242f495280beb4291e64bee2a17f330902e499882fe8e1e2aa875519cab33"},
    "created_at": 1575890427
}

废弃的字段

scopesexpires_in_seconds 字段分别是 scopeexpires_in 的别名。 这么做是为了回避 doorkeeper 5.0.2 引入的向前不兼容。

不要依赖 scopesexpires_in_seconds,它们会在未来版本中移除。

销毁一个 access_token

您可使用 /oauth/revoke API 销毁 access_token。这个 API 会在成功时返回 200 状态码和一个空的 JSON 对象。

parameters = 'client_id=APP_ID&client_secret=APP_SECRET&token=TOKEN'
RestClient.post 'https://gitlab.example.com/oauth/revoke', parameters

OAuth 2.0 令牌在极狐GitLab 容器仓库和包仓库上的支持

极狐GitLab 容器仓库和包仓库目前仅部分支持 OAuth 2.0 令牌: