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/revoke
  • /oauth/token
  • /oauth/userinfo

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

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

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

支持的 OAuth 2.0 流程

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

  • 带有 Proof Key for Code Exchange (PKCE) 的授权码: 最安全,推荐用于客户端和服务器应用程序。如果没有 PKCE,您必须在客户端上保存密钥信息。
  • 授权码: 安全而常见的流程。推荐用于部署于安全环境的服务器端应用程序。
  • 资源所有者密码证书: 仅用于安全托管的第一方服务。建议不要使用此流程。
  • 设备认证授权(极狐GitLab 17.1 及以上版本支持)面向没有浏览器访问权限的设备的安全流程。需要一个辅助设备来完成授权流程。

不支持 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 了。

设备授权流程

  • 引入于极狐GitLab 17.2,并使用名为 oauth2_device_grant_flow 的功能标志。
  • 在极狐GitLab 17.3 中默认启用。
此功能的可用性受控于一个功能标志。有关更多信息,请参阅历史记录。
note 关于设备授权流程的详细描述,可以查看RFC 说明,从设备授权请求到浏览器登录的令牌响应,都有详细描述。

设备授权流程让您的极狐GitLab 在浏览器访问受限的情况下,可以安全地认证您的身份。

此项功能对于那些从无头服务器(headless servers)或没有用户界面的设备上访问极狐GitLab 服务的用户来说非常有用。

  1. 要请求设备授权,请求从输入受限的客户端发送到 https://gitlab.example.com/oauth/authorize_device。例如:

      parameters = 'client_id=UID&scope=read'
      RestClient.post 'https://gitlab.example.com/oauth/authorize_device', parameters
    

    请求成功后,会返回给用户一个包含 verification_uri 的响应。比如:

    {
        "device_code": "GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS",
        "user_code": "0A44L90H",
        "verification_uri": "https://gitlab.example.com/oauth/device",
        "verification_uri_complete": "https://gitlab.example.com/oauth/device?user_code=0A44L90H",
        "expires_in": 300,
        "interval": 5
    }
    
  2. 设备客户端从响应中提取 user_codeverification_uri,并将其显示给用户。然后用户在第二个具有浏览器访问的设备上:
    1. 访问提供的 URI。
    2. 输入用户代码。
    3. 按照提示完成身份验证。
  3. 在立即显示 verification_uriuser_code 之后,设备客户端开始使用关联的 device_code 从初始响应中轮询令牌端点:

    parameters = 'grant_type=urn:ietf:params:oauth:grant-type:device_code
    &device_code=GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS
    &client_id=1406020730'
    RestClient.post 'https://gitlab.example.com/oauth/token', parameters
    
  4. 设备客户端从令牌端点接受响应。如果授权成功,会返回成功的响应,否则返回错误响应。潜在的错误响应可能会根据以下内容进行分类:
    • 由 OAuth 授权框架定义的访问令牌错误响应。
    • 由此处描述的设备授权授予流程特定的错误响应。 这些特定于设备流的错误响应将在以下内容中描述。有关每个潜在响应的更多信息,请参阅相关的RFC 设备授权授予流程规范RFC 授权令牌规范

    示例响应:

    {
      "error": "authorization_pending",
      "error_description": "..."
    }
    

    在收到此响应时,设备客户端继续轮询。

    入如果轮训间隔太短,会收到一个慢速错误响应。例如:

     {
       "error": "slow_down",
       "error_description": "..."
     }
    

    在收到此响应时,设备客户端应减少轮询速率并继续以新的速率轮询。

    如果在授权完成前设备码过期了,则会收到一个过期令牌错误响应。例如:

    {
      "error": "expired_token",
      "error_description": "..."
    }
    

    在此时,设备客户端应停止轮询并重新开始设备授权流程。

    如果授权请求被拒绝,会返回一个访问拒绝错误响应。例如:

    {
      "error": "access_denied",
      "error_description": "..."
    }
    

    授权请求已经被拒绝。用户应该验证他们的凭据或联系他们的系统管理员。

  5. 如果授权成功,会返回一个成功的响应。例如:

    {
        "access_token": "TOKEN",
        "token_type": "Bearer",
        "expires_in": 7200,
        "scope": "read",
        "created_at": 1593096829
    }
    

在此时,设备授权流程就完成了。返回的 access_token 可以提供给极狐GitLab 来认证用户身份,例如在通过 HTTPS 克隆时或访问 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 启用了双重身份认证的用户无法使用自己的密码作为凭据进行此流程。这些用户可以转而使用个人访问令牌
note 确保勾选了 允许通过 HTTP(S) 的 Git 密码认证 选项,以支持密码凭据流程。

在此流程中,通过资源拥有者凭据(用户名和密码)来请求令牌。

凭据应该是以下情况中使用:

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

尽管本授权流程需要直接使用用户的用户名和密码,前述凭据只在一开始申请 access_token 时使用一次。 有了 access_tokenrefresh_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 访问极狐GitLab 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 令牌: